blob: b745defb1e7a1504d527e5941248c6c8c5b38cda [file] [log] [blame]
// Copyright 2022 Google LLC
//
// 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
//
// https://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.
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use core::fmt;
use core::fmt::Write;
use hashbrown::HashMap;
use cantrip_line_reader::LineReader;
use cantrip_memory_interface::*;
#[cfg(feature = "ml_support")]
use cantrip_ml_interface::*;
use cantrip_os_common::cspace_slot::CSpaceSlot;
use cantrip_os_common::sel4_sys;
use cantrip_proc_interface::cantrip_proc_ctrl_get_bundle_state;
use cantrip_proc_interface::cantrip_proc_ctrl_get_running_bundles;
use cantrip_proc_interface::cantrip_proc_ctrl_start;
use cantrip_proc_interface::cantrip_proc_ctrl_stop;
use cantrip_proc_interface::BundleState;
use cantrip_security_interface::cantrip_security_delete_key;
use cantrip_security_interface::cantrip_security_get_packages;
use cantrip_security_interface::cantrip_security_load_application;
use cantrip_security_interface::cantrip_security_read_key;
use cantrip_security_interface::cantrip_security_write_key;
use sel4_sys::seL4_CPtr;
use cantrip_io as io;
#[cfg(any(
feature = "dynamic_load_support",
all(feature = "CONFIG_DEBUG_BUILD", feature = "FRINGE_CMDS"),
))]
mod rz;
#[cfg(feature = "dynamic_load_support")]
mod dynamic_load;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "FRINGE_CMDS"))]
mod fringe_cmds;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_GLOBAL_ALLOCATOR"))]
mod test_global_allocator;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_MEMORY_MANAGER"))]
mod test_memory_manager;
#[cfg(all(
feature = "ml_support",
feature = "CONFIG_DEBUG_BUILD",
feature = "TEST_ML_COORDINATOR"
))]
mod test_ml_coordinator;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_PANIC"))]
mod test_panic;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_SECURITY_COORDINATOR"))]
mod test_security_coordinator;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_TIMER_SERVICE"))]
mod test_timer_service;
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_UART"))]
mod test_uart;
extern "C" {
static SELF_CNODE: seL4_CPtr;
}
/// Error type indicating why a command line is not runnable.
pub enum CommandError {
UnknownCommand,
BadArgs,
IO,
Memory,
Formatter(fmt::Error),
}
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CommandError::UnknownCommand => write!(f, "unknown command"),
CommandError::BadArgs => write!(f, "invalid arguments"),
CommandError::IO => write!(f, "input / output error"),
CommandError::Memory => write!(f, "memory allocation error"),
CommandError::Formatter(e) => write!(f, "{}", e),
}
}
}
impl From<core::num::ParseIntError> for CommandError {
fn from(_err: core::num::ParseIntError) -> CommandError { CommandError::BadArgs }
}
impl From<core::num::ParseFloatError> for CommandError {
fn from(_err: core::num::ParseFloatError) -> CommandError { CommandError::BadArgs }
}
impl From<core::str::ParseBoolError> for CommandError {
fn from(_err: core::str::ParseBoolError) -> CommandError { CommandError::BadArgs }
}
impl From<fmt::Error> for CommandError {
fn from(err: fmt::Error) -> CommandError { CommandError::Formatter(err) }
}
impl From<io::Error> for CommandError {
fn from(_err: io::Error) -> CommandError { CommandError::IO }
}
type CmdFn = fn(
args: &mut dyn Iterator<Item = &str>,
input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError>;
fn get_cmds() -> HashMap<&'static str, CmdFn> {
let mut cmds = HashMap::<&str, CmdFn>::new();
cmds.extend([
("builtins", packages_command as CmdFn), // NB: for backwards compat
("bundles", bundles_command as CmdFn),
("capscan", capscan_command as CmdFn),
("kvdelete", kvdelete_command as CmdFn),
("kvread", kvread_command as CmdFn),
("kvwrite", kvwrite_command as CmdFn),
("loglevel", loglevel_command as CmdFn),
("mdebug", mdebug_command as CmdFn),
("mstats", mstats_command as CmdFn),
("packages", packages_command as CmdFn),
("ps", ps_command as CmdFn),
#[cfg(feature = "timer_support")]
("sleep", sleep_command as CmdFn),
("source", source_command as CmdFn),
("start", start_command as CmdFn),
("stop", stop_command as CmdFn),
("status", status_command as CmdFn),
]);
#[cfg(feature = "ml_support")]
cmds.extend([("mldebug", state_mlcoord_command as CmdFn)]);
#[cfg(feature = "dynamic_load_support")]
dynamic_load::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "FRINGE_CMDS"))]
fringe_cmds::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_GLOBAL_ALLOCATOR"))]
test_global_allocator::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_MEMORY_MANAGER"))]
test_memory_manager::add_cmds(&mut cmds);
#[cfg(all(
feature = "ml_support",
feature = "CONFIG_DEBUG_BUILD",
feature = "TEST_ML_COORDINATOR"
))]
test_ml_coordinator::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_PANIC"))]
test_panic::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_SECURITY_COORDINATOR"))]
test_security_coordinator::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_TIMER_SERVICE"))]
test_timer_service::add_cmds(&mut cmds);
#[cfg(all(feature = "CONFIG_DEBUG_BUILD", feature = "TEST_UART"))]
test_uart::add_cmds(&mut cmds);
cmds
}
pub fn eval<T: io::BufRead>(
cmdline: &str,
cmds: &HashMap<&str, CmdFn>,
output: &mut dyn io::Write,
input: &mut T,
) {
let mut args = cmdline.split_ascii_whitespace();
match args.next() {
Some("?") | Some("help") => {
let mut keys: Vec<&str> = cmds.keys().copied().collect();
keys.sort_unstable();
for k in keys {
let _ = writeln!(output, "{}", k);
}
}
Some(cmd) => {
let result = cmds.get(cmd).map_or_else(
|| Err(CommandError::UnknownCommand),
|func| func(&mut args, input, output),
);
if let Err(e) = result {
let _ = writeln!(output, "{}: {}", e, cmd);
};
}
None => {
let _ = output.write_str("\n");
}
}
}
/// Read-eval-print loop for the DebugConsole command line interface.
pub fn repl<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T) -> ! {
let cmds = get_cmds();
let mut line_reader = LineReader::new();
loop {
const PROMPT: &str = "CANTRIP> ";
let _ = output.write_str(PROMPT);
match line_reader.read_line(output, input) {
Ok(cmdline) => eval(cmdline, &cmds, output, input),
Err(e) => {
let _ = writeln!(output, "\n{}", e);
}
}
}
}
/// Stripped down repl for running automation scripts. Like repl but prints
/// each cmd line and stops at EOF/error.
pub fn repl_eof<T: io::BufRead>(output: &mut dyn io::Write, input: &mut T) {
let cmds = get_cmds();
let mut line_reader = LineReader::new();
loop {
// NB: LineReader echo's input
let _ = write!(output, "CANTRIP> ");
if let Ok(cmdline) = line_reader.read_line(output, input) {
eval(cmdline, &cmds, output, input);
} else {
let _ = writeln!(output, "EOF");
break;
}
}
}
/// Implements a command that pauses for a specified period of time.
#[cfg(feature = "timer_support")]
fn sleep_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
_output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let time_str = args.next().ok_or(CommandError::BadArgs)?;
let time_ms = time_str.parse::<u32>()?;
use cantrip_timer_interface::*;
match cantrip_timer_oneshot(0, time_ms) {
Ok(_) => {
cantrip_timer_wait().or(Err(CommandError::IO))?;
Ok(())
}
_ => Err(CommandError::BadArgs),
}
}
/// Implements a command that interprets commands from an installed package.
fn source_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let (quiet, filename) = match args.next().ok_or(CommandError::BadArgs)? {
"-q" => (true, args.next().ok_or(CommandError::BadArgs)?),
filename => (false, filename),
};
let mut container_slot = CSpaceSlot::new();
match cantrip_security_load_application(filename, &container_slot) {
Ok(frames) => {
container_slot.release(); // NB: take ownership
let mut script_input = io::BufReader::new(cantrip_io_objdesc::Rx::new(&frames));
repl_eof(output, &mut script_input);
// NB: must drop refs before freeing the bundle
drop(script_input);
let _ = cantrip_object_free_in_cnode(&frames);
}
Err(status) => {
if !quiet {
writeln!(output, "source {} failed: {:?}", filename, status)?
};
}
}
Ok(())
}
/// Implements a command that lists the available packages.
fn packages_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
match cantrip_security_get_packages() {
Ok(bundle_ids) => {
for b in bundle_ids {
writeln!(output, "{}", b)?;
}
}
Err(status) => {
writeln!(output, "get_packages failed: {:?}", status)?;
}
}
Ok(())
}
/// Implements a command to configure the max log level for the DebugConsole.
fn loglevel_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
if let Some(level) = args.next() {
use log::LevelFilter;
match level {
"off" => log::set_max_level(LevelFilter::Off),
"debug" => log::set_max_level(LevelFilter::Debug),
"info" => log::set_max_level(LevelFilter::Info),
"error" => log::set_max_level(LevelFilter::Error),
"trace" => log::set_max_level(LevelFilter::Trace),
"warn" => log::set_max_level(LevelFilter::Warn),
_ => writeln!(output, "Unknown log level {}", level)?,
}
}
Ok(writeln!(output, "{}", log::max_level())?)
}
/// Implements a "ps" command that dumps seL4 scheduler state to the console.
#[allow(unused_variables)]
fn ps_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
#[cfg(feature = "CONFIG_DEBUG_BUILD")]
unsafe {
sel4_sys::seL4_DebugDumpScheduler();
Ok(())
}
#[cfg(not(feature = "CONFIG_DEBUG_BUILD"))]
Ok(writeln!(
output,
"Kernel support not configured with CONFIG_DEBUG_BUILD!"
)?)
}
fn bundles_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
match cantrip_proc_ctrl_get_running_bundles() {
Ok(bundle_ids) => {
for b in bundle_ids {
writeln!(output, "{}", b)?;
}
}
Err(status) => {
writeln!(output, "get_running_bundles failed: {:?}", status)?;
}
}
Ok(())
}
/// Implements a "capscan" command that dumps seL4 capabilities to the console.
#[allow(unused_variables)]
fn capscan_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
#[cfg(feature = "CONFIG_PRINTING")]
match args.next() {
Some("console") => unsafe {
sel4_sys::seL4_DebugDumpCNode(SELF_CNODE);
},
Some("memory") => {
let _ = cantrip_memory_interface::cantrip_memory_capscan();
}
Some("process") => {
let _ = cantrip_proc_interface::cantrip_proc_ctrl_capscan();
}
#[cfg(feature = "ml_support")]
Some("mlcoord") => {
let _ = cantrip_mlcoord_capscan();
}
Some("sdk") => {
let _ = cantrip_sdk_manager::cantrip_sdk_manager_capscan();
}
Some("security") => {
let _ = cantrip_security_interface::cantrip_security_capscan();
}
#[cfg(feature = "timer_support")]
Some("timer") => {
let _ = cantrip_timer_interface::cantrip_timer_capscan();
}
Some(bundle_id) => {
if let Err(e) = cantrip_proc_interface::cantrip_proc_ctrl_capscan_bundle(bundle_id) {
writeln!(output, "{}: {:?}", bundle_id, e)?;
}
}
None => {
writeln!(output, "capscan <target>, where <target> is one of:")?;
writeln!(output, " console (DebugConsole)")?;
writeln!(output, " memory (MemoryManager)")?;
#[cfg(feature = "ml_support")]
writeln!(output, " mlcoord (MlCoordinator)")?;
writeln!(output, " process (ProcessManager)")?;
writeln!(output, " sdk (SDKRuntime)")?;
writeln!(output, " securiy (SecurityCoordinator)")?;
#[cfg(feature = "timer_support")]
writeln!(output, " timer (TimerService)")?;
writeln!(output, "anything else is treated as a bundle_id")?;
}
}
#[cfg(not(feature = "CONFIG_PRINTING"))]
writeln!(output, "Kernel not configured with CONFIG_PRINTING!")?;
Ok(())
}
fn start_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
match cantrip_proc_ctrl_start(bundle_id) {
Ok(_) => {
writeln!(output, "Bundle \"{}\" started.", bundle_id)?;
}
Err(status) => {
writeln!(output, "start failed: {:?}", status)?;
}
}
Ok(())
}
fn stop_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
match cantrip_proc_ctrl_stop(bundle_id) {
Ok(_) => {
writeln!(output, "Bundle \"{}\" stopped.", bundle_id)?;
}
Err(status) => {
writeln!(output, "stop failed: {:?}", status)?;
}
}
Ok(())
}
fn status_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
match cantrip_proc_ctrl_get_bundle_state(bundle_id) {
Ok(state) => match state {
BundleState::Exited(code) => {
writeln!(output, "{}: Exited({:?})", bundle_id, code)?;
}
_ => {
writeln!(output, "{}: {:?}", bundle_id, state)?;
}
},
Err(status) => {
writeln!(output, "status failed: {:?}", status)?;
}
}
Ok(())
}
fn kvdelete_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
let key = args.next().ok_or(CommandError::BadArgs)?;
match cantrip_security_delete_key(bundle_id, key) {
Ok(_) => {
writeln!(output, "Delete key \"{}\".", key)?;
}
Err(status) => {
writeln!(output, "Delete key \"{}\" failed: {:?}", key, status)?;
}
}
Ok(())
}
fn kvread_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
let key = args.next().ok_or(CommandError::BadArgs)?;
match cantrip_security_read_key(bundle_id, key) {
Ok(keyval) => {
writeln!(output, "Read key \"{}\" = {:?}.", key, keyval)?;
}
Err(status) => {
writeln!(output, "Read key \"{}\" failed: {:?}", key, status)?;
}
}
Ok(())
}
fn kvwrite_command(
args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let bundle_id = args.next().ok_or(CommandError::BadArgs)?;
let key = args.next().ok_or(CommandError::BadArgs)?;
let value = args.collect::<Vec<&str>>().join(" ");
match cantrip_security_write_key(bundle_id, key, value.as_bytes()) {
Ok(_) => {
writeln!(output, "Write key \"{}\" = {:?}.", key, value)?;
}
Err(status) => {
writeln!(output, "Write key \"{}\" failed: {:?}", key, status)?;
}
}
Ok(())
}
fn mdebug_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
if let Err(status) = cantrip_memory_debug() {
writeln!(output, "stats failed: {:?}", status)?;
}
Ok(())
}
fn mstats(output: &mut dyn io::Write, stats: &MemoryManagerStats) -> Result<(), CommandError> {
writeln!(
output,
"{} bytes in-use, {} bytes free, {} bytes requested, {} overhead",
stats.allocated_bytes, stats.free_bytes, stats.total_requested_bytes, stats.overhead_bytes
)?;
writeln!(
output,
"{} objs in-use, {} objs requested",
stats.allocated_objs, stats.total_requested_objs
)?;
Ok(())
}
fn mstats_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
output: &mut dyn io::Write,
) -> Result<(), CommandError> {
match cantrip_memory_stats() {
Ok(stats) => {
mstats(output, &stats)?;
}
Err(status) => {
writeln!(output, "stats failed: {:?}", status)?;
}
}
Ok(())
}
#[cfg(feature = "ml_support")]
fn state_mlcoord_command(
_args: &mut dyn Iterator<Item = &str>,
_input: &mut dyn io::BufRead,
_output: &mut dyn io::Write,
) -> Result<(), CommandError> {
cantrip_mlcoord_debug_state();
Ok(())
}