use epics_rtems_boot::shell::{self, ShellError};
use epics_rtems_boot::stats::{self, HeapSpace};
use super::registry::*;
pub fn register_rtems_commands() {
for def in rtems_command_defs() {
super::register_command(def);
}
}
fn rtems_command_defs() -> Vec<CommandDef> {
vec![
cmd_netstat(),
cmd_heap_space(),
cmd_zoneset(),
cmd_rt(),
cmd_setlogmask(),
]
}
fn cmd_netstat() -> CommandDef {
CommandDef::new(
"netstat",
vec![ArgDesc {
name: "level",
arg_type: ArgType::Int,
}],
"show network status",
|args: &[ArgValue], _ctx: &CommandContext| {
let level = match args.first() {
Some(ArgValue::Int(n)) => *n as i32,
_ => 0,
};
shell::netstat(level).map_err(|e| e.to_string())?;
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_heap_space() -> CommandDef {
CommandDef::new(
"heapSpace",
vec![],
"show malloc statistic",
|_args: &[ArgValue], ctx: &CommandContext| {
let Some(heap) = stats::heap_space() else {
return Err("heapSpace: no malloc statistics on this build".to_string());
};
ctx.print_fmt(format_args!("{}", heap_space_line(heap)));
Ok(CommandOutcome::Continue)
},
)
}
fn heap_space_line(heap: HeapSpace) -> String {
let free = heap.free() as f64;
if free >= (1024 * 1024) as f64 {
format!("Heap space: {:.1} MB", free / (1024.0 * 1024.0))
} else {
format!("Heap space: {:.1} kB", free / 1024.0)
}
}
fn cmd_zoneset() -> CommandDef {
CommandDef::new(
"zoneset",
vec![ArgDesc {
name: "zone string",
arg_type: ArgType::String,
}],
"set timezone (obsolete?)",
|args: &[ArgValue], _ctx: &CommandContext| {
let zone = match args.first() {
Some(ArgValue::String(s)) => Some(s.as_str()),
_ => None,
};
unsafe { shell::zoneset(zone) }.map_err(|e| e.to_string())?;
Ok(CommandOutcome::Continue)
},
)
}
fn cmd_rt() -> CommandDef {
CommandDef::new(
"rt",
vec![
ArgDesc {
name: "cmd",
arg_type: ArgType::String,
},
ArgDesc {
name: "args",
arg_type: ArgType::Argv,
},
],
"run rtems shell command",
|args: &[ArgValue], _ctx: &CommandContext| {
let argv = rt_argv(args);
match shell::run_shell_command(&argv) {
Ok(0) => Ok(CommandOutcome::Continue),
Ok(status) => Err(format!("ERR: {status}")),
Err(ShellError::NoSuchCommand) => Err("ERR: No such command".to_string()),
Err(other) => Err(other.to_string()),
}
},
)
}
fn rt_argv(args: &[ArgValue]) -> Vec<String> {
let mut argv = Vec::new();
if let Some(ArgValue::String(cmd)) = args.first() {
argv.push(cmd.clone());
}
if let Some(ArgValue::Argv(rest)) = args.get(1) {
argv.extend(rest.iter().cloned());
}
argv
}
fn cmd_setlogmask() -> CommandDef {
CommandDef::new(
"setlogmask",
vec![ArgDesc {
name: "level name",
arg_type: ArgType::String,
}],
"Set syslog() threshold level",
|args: &[ArgValue], ctx: &CommandContext| {
let Some(ArgValue::String(name)) = args.first() else {
ctx.print_fmt(format_args!(
"{}",
setlogmask_usage(&shell::log_priority_names())
));
return Ok(CommandOutcome::Continue);
};
match shell::set_log_priority(name) {
Ok(()) => Ok(CommandOutcome::Continue),
Err(ShellError::UnknownLevel) => Err("Error: unknown log level.".to_string()),
Err(other) => Err(other.to_string()),
}
},
)
}
fn setlogmask_usage(levels: &[String]) -> String {
let mut out = String::from("Usage: setlogmask <level>\n\n Level names:");
for level in levels {
out.push_str("\n ");
out.push_str(level);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_set_is_c_s_names_and_arities() {
let defs = rtems_command_defs();
let shape: Vec<(String, usize)> = defs
.iter()
.map(|d| (d.name.clone(), d.args.len()))
.collect();
assert_eq!(
shape,
vec![
("netstat".to_string(), 1),
("heapSpace".to_string(), 0),
("zoneset".to_string(), 1),
("rt".to_string(), 2),
("setlogmask".to_string(), 1),
],
"C `iocshRegisterRTEMS` order, names and argument counts"
);
}
#[test]
fn nfs_mount_is_not_registered() {
assert!(!rtems_command_defs().iter().any(|d| d.name == "nfsMount"));
}
#[test]
fn the_usage_text_is_c_s() {
let defs = rtems_command_defs();
let usage = |name: &str| {
defs.iter()
.find(|d| d.name == name)
.expect("registered above")
.usage
.clone()
};
assert_eq!(usage("netstat"), "show network status");
assert_eq!(usage("heapSpace"), "show malloc statistic");
assert_eq!(usage("zoneset"), "set timezone (obsolete?)");
assert_eq!(usage("rt"), "run rtems shell command");
assert_eq!(usage("setlogmask"), "Set syslog() threshold level");
}
#[test]
fn the_argument_descriptors_are_c_s() {
let defs = rtems_command_defs();
let arg = |name: &str, i: usize| {
let d = defs.iter().find(|d| d.name == name).expect("registered");
(
d.args[i].name,
format!("{:?}", d.args[i].arg_type).to_string(),
)
};
assert_eq!(arg("netstat", 0), ("level", "Int".to_string()));
assert_eq!(arg("zoneset", 0), ("zone string", "String".to_string()));
assert_eq!(arg("rt", 0), ("cmd", "String".to_string()));
assert_eq!(arg("rt", 1), ("args", "Argv".to_string()));
assert_eq!(arg("setlogmask", 0), ("level name", "String".to_string()));
}
#[test]
fn the_heap_space_line_is_c_s_two_branches() {
let heap = |free: u64| HeapSpace {
size: free,
lifetime_allocated: 0,
lifetime_freed: 0,
};
assert_eq!(
heap_space_line(heap(1024 * 1024)),
"Heap space: 1.0 MB",
"the boundary itself is the MB branch in C"
);
assert_eq!(
heap_space_line(heap(1024 * 1024 - 1)),
"Heap space: 1024.0 kB",
"one byte below it is still kB, as in C"
);
assert_eq!(heap_space_line(heap(0)), "Heap space: 0.0 kB");
assert_eq!(
heap_space_line(HeapSpace {
size: 8 * 1024 * 1024,
lifetime_allocated: 6 * 1024 * 1024,
lifetime_freed: 2 * 1024 * 1024,
}),
"Heap space: 4.0 MB",
"C subtracts the live bytes from the heap size"
);
}
#[test]
fn rt_puts_the_command_name_back_at_argv_zero() {
let args = vec![
ArgValue::String("stackuse".to_string()),
ArgValue::Argv(vec!["-v".to_string(), "2".to_string()]),
];
assert_eq!(rt_argv(&args), vec!["stackuse", "-v", "2"]);
}
#[test]
fn rt_with_no_arguments_still_passes_the_name() {
let args = vec![
ArgValue::String("stackuse".to_string()),
ArgValue::Argv(vec![]),
];
assert_eq!(rt_argv(&args), vec!["stackuse"]);
}
#[test]
fn a_bare_rt_produces_an_empty_argv() {
assert!(rt_argv(&[ArgValue::Missing, ArgValue::Argv(vec![])]).is_empty());
}
#[test]
fn the_setlogmask_usage_is_c_s_block() {
let levels = vec!["emerg".to_string(), "alert".to_string()];
assert_eq!(
setlogmask_usage(&levels),
"Usage: setlogmask <level>\n\n Level names:\n emerg\n alert"
);
}
#[test]
fn the_setlogmask_usage_survives_an_empty_level_list() {
assert_eq!(
setlogmask_usage(&[]),
"Usage: setlogmask <level>\n\n Level names:"
);
}
}