1use crate::rt;
5use core::str::FromStr;
6use mycelium_util::fmt::{self, Write};
7
8#[derive(Debug)]
11pub struct Command<'cmd> {
12 name: &'cmd str,
13 help: &'cmd str,
14 run: Option<RunKind<'cmd>>,
15 usage: &'cmd str,
16 subcommands: Option<&'cmd [Command<'cmd>]>,
17}
18
19#[derive(Debug)]
20pub struct Error<'a> {
21 line: &'a str,
22 kind: ErrorKind<'a>,
23}
24
25pub type CmdResult<'a> = core::result::Result<(), Error<'a>>;
26
27pub trait Run: Send + Sync {
28 fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx>;
29}
30
31#[derive(Debug)]
32enum ErrorKind<'a> {
33 UnknownCommand(&'a [Command<'a>]),
34
35 SubcommandRequired(&'a [Command<'a>]),
36 InvalidArguments {
37 help: &'a str,
38 arg: &'a str,
39 flag: Option<&'a str>,
40 },
41 FlagRequired {
42 flags: &'a [&'a str],
43 },
44 Other(&'static str),
45}
46
47enum RunKind<'a> {
48 Fn(fn(Context<'_>) -> CmdResult<'_>),
49 Runnable(&'a dyn Run),
50}
51
52pub fn eval(line: &str) {
53 static COMMANDS: &[Command] = &[
54 DUMP,
55 SLEEP,
56 PANIC,
57 FAULT,
58 VERSION,
59 crate::drivers::pci::LSPCI_CMD,
60 ];
61
62 let _span = tracing::info_span!(target: "shell", "$", message = %line).entered();
63 tracing::info!(target: "shell", "");
64
65 if line == "help" {
66 tracing::info!(target: "shell", "available commands:");
67 print_help("", COMMANDS);
68 tracing::info!(target: "shell", "");
69 return;
70 }
71
72 match handle_command(Context::new(line), COMMANDS) {
73 Ok(_) => {}
74 Err(error) => tracing::error!(target: "shell", "error: {error}"),
75 }
76
77 tracing::info!(target: "shell", "");
78}
79
80#[derive(Copy, Clone)]
81pub struct Context<'cmd> {
82 line: &'cmd str,
83 current: &'cmd str,
84}
85
86pub fn handle_command<'cmd>(ctx: Context<'cmd>, commands: &'cmd [Command]) -> CmdResult<'cmd> {
87 let chunk = ctx.current.trim();
88 for cmd in commands {
89 if let Some(current) = chunk.strip_prefix(cmd.name) {
90 let current = current.trim();
91 return cmd.run(Context { current, ..ctx });
92 }
93 }
94
95 Err(ctx.unknown_command(commands))
96}
97
98const DUMP: Command = Command::new("dump")
101 .with_help("print formatted representations of a kernel structure")
102 .with_subcommands(&[
103 Command::new("bootinfo")
104 .with_help("print the boot information structure")
105 .with_fn(|ctx| Err(ctx.other_error("not yet implemented"))),
106 Command::new("archinfo")
107 .with_help("print the architecture information structure")
108 .with_fn(|ctx| Err(ctx.other_error("not yet implemented"))),
109 rt::DUMP_RT,
116 crate::arch::shell::DUMP_ARCH,
117 Command::new("heap")
118 .with_help("print kernel heap statistics")
119 .with_fn(|_| {
120 tracing::info!(target: "shell", heap = ?crate::ALLOC.state());
121 Ok(())
122 }),
123 ]);
124
125const SLEEP: Command = Command::new("sleep")
126 .with_help("spawns a task to sleep for SECONDS")
127 .with_usage("<SECONDS>")
128 .with_fn(|ctx| {
129 use maitake::time;
130
131 let line = ctx.command().trim();
132 if line.is_empty() {
133 return Err(ctx.invalid_argument("expected a number of seconds to sleep for"));
134 }
135
136 let secs: u64 = line
137 .parse()
138 .map_err(|_| ctx.invalid_argument("number of seconds must be an integer"))?;
139 let duration = time::Duration::from_secs(secs);
140
141 tracing::info!(target: "shell", ?duration, "spawning a sleep");
142 rt::spawn(async move {
143 time::sleep(duration).await;
144 tracing::info!(target: "shell", ?duration, "slept");
145 });
146
147 Ok(())
148 });
149
150const PANIC: Command = Command::new("panic")
151 .with_usage("<MESSAGE>")
152 .with_help("cause a kernel panic with the given message. use with caution.")
153 .with_fn(|line| {
154 panic!("{}", line.current);
155 });
156
157const FAULT: Command = Command::new("fault")
158 .with_help("cause a CPU fault (divide-by-zero). use with caution.")
159 .with_fn(|_| {
160 unsafe {
161 core::arch::asm!(
162 "div {0:e}",
163 in(reg) 0,
164 )
165 }
166 Ok(())
167 });
168
169const VERSION: Command = Command::new("version")
170 .with_help("print verbose build and version info.")
171 .with_fn(|_| {
172 tracing::info!("Mycelium v{}", env!("CARGO_PKG_VERSION"));
173 tracing::info!(build.version = %crate::MYCELIUM_VERSION);
174 tracing::info!(build.timestamp = %env!("VERGEN_BUILD_TIMESTAMP"));
175 tracing::info!(build.features = %env!("VERGEN_CARGO_FEATURES"));
176 tracing::info!(build.profile = %env!("VERGEN_CARGO_PROFILE"));
177 tracing::info!(build.target = %env!("VERGEN_CARGO_TARGET_TRIPLE"));
178 tracing::info!(commit.sha = %env!("VERGEN_GIT_SHA"));
179 tracing::info!(commit.branch = %env!("VERGEN_GIT_BRANCH"));
180 tracing::info!(commit.date = %env!("VERGEN_GIT_COMMIT_TIMESTAMP"));
181 tracing::info!(rustc.version = %env!("VERGEN_RUSTC_SEMVER"));
182 tracing::info!(rustc.channel = %env!("VERGEN_RUSTC_CHANNEL"));
183
184 Ok(())
185 });
186
187impl<'cmd> Command<'cmd> {
190 #[must_use]
199 pub const fn new(name: &'cmd str) -> Self {
200 Self {
201 name,
202 help: "",
203 usage: "",
204 run: None,
205 subcommands: None,
206 }
207 }
208
209 #[must_use]
227 pub const fn with_help(self, help: &'cmd str) -> Self {
228 Self { help, ..self }
229 }
230
231 #[must_use]
251 pub const fn with_usage(self, usage: &'cmd str) -> Self {
252 Self { usage, ..self }
253 }
254
255 #[must_use]
309 pub const fn with_subcommands(self, subcommands: &'cmd [Self]) -> Self {
310 Self {
311 subcommands: Some(subcommands),
312 ..self
313 }
314 }
315
316 #[must_use]
321 pub const fn with_fn(self, func: fn(Context<'_>) -> CmdResult<'_>) -> Self {
322 Self {
323 run: Some(RunKind::Fn(func)),
324 ..self
325 }
326 }
327
328 #[must_use]
335 pub const fn with_runnable(self, run: &'cmd dyn Run) -> Self {
336 Self {
337 run: Some(RunKind::Runnable(run)),
338 ..self
339 }
340 }
341
342 pub fn run<'ctx>(&'cmd self, ctx: Context<'ctx>) -> CmdResult<'ctx>
344 where
345 'cmd: 'ctx,
346 {
347 let current = ctx.current.trim();
348
349 if current == "help" {
350 let name = ctx.line.strip_suffix(" help").unwrap_or("<???BUG???>");
351 if let Some(subcommands) = self.subcommands {
352 tracing::info!(target: "shell", "{name} <COMMAND>: {help}", help = self.help);
353 tracing::info!(target: "shell", "commands:");
354 print_help(name, subcommands);
355 } else {
356 tracing::info!(target: "shell", "{name}");
357 }
358 return Ok(());
359 }
360
361 if let Some(subcommands) = self.subcommands {
362 return match handle_command(ctx, subcommands) {
363 Err(e) if e.is_unknown_command() => {
364 if let Some(ref run) = self.run {
365 run.run(ctx)
366 } else if current.is_empty() {
367 Err(ctx.subcommand_required(subcommands))
368 } else {
369 Err(e)
370 }
371 }
372 res => res,
373 };
374 }
375
376 self.run
377 .as_ref()
378 .ok_or_else(|| ctx.subcommand_required(self.subcommands.unwrap_or(&[])))
379 .and_then(|run| run.run(ctx))
380 }
381}
382
383impl fmt::Display for Command<'_> {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 let Self {
386 run: _func,
387 name,
388 help,
389 usage,
390 subcommands: _subcommands,
391 } = self;
392
393 let usage = if self.subcommands.is_some() && usage.is_empty() {
394 "<COMMAND>"
395 } else {
396 usage
397 };
398
399 write!(
400 f,
401 "{name}{usage_pad}{usage} --- {help}",
402 usage_pad = if !usage.is_empty() { " " } else { "" },
403 )
404 }
405}
406
407impl Error<'_> {
410 fn is_unknown_command(&self) -> bool {
411 matches!(self.kind, ErrorKind::UnknownCommand(_))
412 }
413}
414
415impl fmt::Display for Error<'_> {
416 fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 fn command_names<'cmd>(
418 cmds: &'cmd [Command<'cmd>],
419 ) -> impl Iterator<Item = &'cmd str> + 'cmd {
420 cmds.iter()
421 .map(|Command { name, .. }| *name)
422 .chain(core::iter::once("help"))
423 }
424
425 fn fmt_flag_names(f: &mut fmt::Formatter<'_>, flags: &[&str]) -> fmt::Result {
426 let mut names = flags.iter();
427 if let Some(name) = names.next() {
428 f.write_str(name)?;
429 for name in names {
430 write!(f, "|{name}")?;
431 }
432 }
433 Ok(())
434 }
435
436 let Self { line, kind } = self;
437 match kind {
438 ErrorKind::UnknownCommand(commands) => {
439 write!(f, "unknown command {line:?}, expected one of: [")?;
440 fmt::comma_delimited(&mut f, command_names(commands))?;
441 f.write_char(']')?;
442 }
443 ErrorKind::InvalidArguments { arg, help, flag } => {
444 f.write_str("invalid argument")?;
445 if let Some(flag) = flag {
446 write!(f, " {flag}")?;
447 }
448 write!(f, " {arg:?}: {help}")?;
449 }
450 ErrorKind::SubcommandRequired(subcommands) => {
451 writeln!(
452 f,
453 "the '{line}' command requires one of the following subcommands: ["
454 )?;
455 fmt::comma_delimited(&mut f, command_names(subcommands))?;
456 f.write_char(']')?;
457 }
458 ErrorKind::FlagRequired { flags } => {
459 write!(f, "the '{line}' command requires the ")?;
460 fmt_flag_names(f, flags)?;
461 write!(f, " flag")?;
462 }
463 ErrorKind::Other(msg) => write!(f, "could not execute {line:?}: {msg}")?,
464 }
465
466 Ok(())
467 }
468}
469
470impl<'cmd> Context<'cmd> {
473 pub const fn new(line: &'cmd str) -> Self {
474 Self {
475 line,
476 current: line,
477 }
478 }
479
480 pub fn command(&self) -> &'cmd str {
481 self.current.trim()
482 }
483
484 fn unknown_command(&self, commands: &'cmd [Command]) -> Error<'cmd> {
485 Error {
486 line: self.line,
487 kind: ErrorKind::UnknownCommand(commands),
488 }
489 }
490
491 fn subcommand_required(&self, subcommands: &'cmd [Command]) -> Error<'cmd> {
492 Error {
493 line: self.line,
494 kind: ErrorKind::SubcommandRequired(subcommands),
495 }
496 }
497
498 pub fn invalid_argument(&self, help: &'static str) -> Error<'cmd> {
499 Error {
500 line: self.line,
501 kind: ErrorKind::InvalidArguments {
502 arg: self.current,
503 flag: None,
504 help,
505 },
506 }
507 }
508
509 pub fn invalid_argument_named(&self, name: &'static str, help: &'static str) -> Error<'cmd> {
510 Error {
511 line: self.line,
512 kind: ErrorKind::InvalidArguments {
513 arg: self.current,
514 flag: Some(name),
515 help,
516 },
517 }
518 }
519
520 pub fn other_error(&self, msg: &'static str) -> Error<'cmd> {
521 Error {
522 line: self.line,
523 kind: ErrorKind::Other(msg),
524 }
525 }
526
527 pub fn parse_bool_flag(&mut self, flag: &str) -> bool {
528 if let Some(rest) = self.command().trim().strip_prefix(flag) {
529 self.current = rest.trim();
530 true
531 } else {
532 false
533 }
534 }
535
536 pub fn parse_optional_u32_hex_or_dec(
537 &mut self,
538 name: &'static str,
539 ) -> Result<Option<u32>, Error<'cmd>> {
540 let (chunk, rest) = match self.command().split_once(" ") {
541 Some((chunk, rest)) => (chunk.trim(), rest),
542 None => (self.command(), ""),
543 };
544
545 if chunk.is_empty() {
546 return Ok(None);
547 }
548
549 let val = if let Some(hex_num) = chunk.strip_prefix("0x") {
550 u32::from_str_radix(hex_num.trim(), 16).map_err(|_| Error {
551 line: self.line,
552 kind: ErrorKind::InvalidArguments {
553 arg: chunk,
554 flag: Some(name),
555 help: "expected a 32-bit hex number",
556 },
557 })?
558 } else {
559 u32::from_str(chunk).map_err(|_| Error {
560 line: self.line,
561 kind: ErrorKind::InvalidArguments {
562 arg: chunk,
563 flag: Some(name),
564 help: "expected a 32-bit decimal number",
565 },
566 })?
567 };
568
569 self.current = rest;
570 Ok(Some(val))
571 }
572
573 pub fn parse_u32_hex_or_dec(&mut self, name: &'static str) -> Result<u32, Error<'cmd>> {
574 self.parse_optional_u32_hex_or_dec(name).and_then(|val| {
575 val.ok_or_else(|| self.invalid_argument_named(name, "expected a number"))
576 })
577 }
578
579 pub fn parse_optional_flag<T>(
580 &mut self,
581 names: &'static [&'static str],
582 ) -> Result<Option<T>, Error<'cmd>>
583 where
584 T: FromStr,
585 T::Err: core::fmt::Display,
586 {
587 for name in names {
588 if let Some(rest) = self.command().strip_prefix(name) {
589 let (chunk, rest) = match rest.trim().split_once(" ") {
590 Some((chunk, rest)) => (chunk.trim(), rest),
591 None => (rest, ""),
592 };
593
594 if chunk.is_empty() {
595 return Err(Error {
596 line: self.line,
597 kind: ErrorKind::InvalidArguments {
598 arg: chunk,
599 flag: Some(name),
600 help: "expected a value",
601 },
602 });
603 }
604
605 match chunk.parse() {
606 Ok(val) => {
607 self.current = rest;
608 return Ok(Some(val));
609 }
610 Err(e) => {
611 tracing::warn!(target: "shell", "invalid value {chunk:?} for flag {name}: {e}");
612 return Err(Error {
613 line: self.line,
614 kind: ErrorKind::InvalidArguments {
615 arg: chunk,
616 flag: Some(name),
617 help: "invalid value",
618 },
619 });
620 }
621 }
622 }
623 }
624
625 Ok(None)
626 }
627
628 pub fn parse_required_flag<T>(
629 &mut self,
630 names: &'static [&'static str],
631 ) -> Result<T, Error<'cmd>>
632 where
633 T: FromStr,
634 T::Err: core::fmt::Display,
635 {
636 self.parse_optional_flag(names).and_then(|val| {
637 val.ok_or(Error {
638 line: self.line,
639 kind: ErrorKind::FlagRequired { flags: names },
640 })
641 })
642 }
643}
644
645impl RunKind<'_> {
648 #[inline]
649 fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx> {
650 match self {
651 Self::Fn(func) => func(ctx),
652 Self::Runnable(runnable) => runnable.run(ctx),
653 }
654 }
655}
656
657impl fmt::Debug for RunKind<'_> {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 match self {
660 Self::Fn(func) => f.debug_tuple("Run::Func").field(&fmt::ptr(func)).finish(),
661 Self::Runnable(runnable) => f
662 .debug_tuple("Run::Runnable")
663 .field(&fmt::ptr(runnable))
664 .finish(),
665 }
666 }
667}
668
669impl<F> Run for F
672where
673 F: Fn(Context<'_>) -> CmdResult<'_> + Send + Sync,
674{
675 fn run<'ctx>(&'ctx self, ctx: Context<'ctx>) -> CmdResult<'ctx> {
676 self(ctx)
677 }
678}
679
680fn print_help(parent_cmd: &str, commands: &[Command]) {
681 let parent_cmd_pad = if parent_cmd.is_empty() { "" } else { " " };
682 for command in commands {
683 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}{command}");
684 }
685 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}help --- prints this help message");
686}
687
688#[derive(Copy, Clone, Debug, Eq, PartialEq)]
689pub enum NumberFormat {
690 Binary,
691 Hex,
692 Decimal,
693}
694
695impl FromStr for NumberFormat {
696 type Err = &'static str;
697 fn from_str(s: &str) -> Result<Self, Self::Err> {
698 match s.trim() {
699 "b" | "bin" | "binary" => Ok(Self::Binary),
700 "h" | "hex" => Ok(Self::Hex),
701 "d" | "dec" | "decimal" => Ok(Self::Decimal),
702 _ => Err("expected one of: [b, bin, binary, h, hex, d, decimal]"),
703 }
704 }
705}