Skip to content

Commit 3d0de2c

Browse files
committed
Add EXEC command.
Lets you execute a script as if the user had typed those bytes into the console.
1 parent e55f281 commit 3d0de2c

File tree

4 files changed

+122
-12
lines changed

4 files changed

+122
-12
lines changed

src/commands/fs.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! File Systems related commands for Neotron OS
22
3-
use embedded_sdmmc::VolumeIdx;
4-
53
use crate::{bios, osprint, osprintln, Ctx};
64

75
pub static DIR_ITEM: menu::Item<Ctx> = menu::Item {
@@ -25,6 +23,18 @@ pub static LOAD_ITEM: menu::Item<Ctx> = menu::Item {
2523
help: Some("Load a file into the application area"),
2624
};
2725

26+
pub static EXEC_ITEM: menu::Item<Ctx> = menu::Item {
27+
item_type: menu::ItemType::Callback {
28+
function: exec,
29+
parameters: &[menu::Parameter::Mandatory {
30+
parameter_name: "file",
31+
help: Some("The shell script to run"),
32+
}],
33+
},
34+
command: "exec",
35+
help: Some("Execute a shell script"),
36+
};
37+
2838
pub static TYPE_ITEM: menu::Item<Ctx> = menu::Item {
2939
item_type: menu::ItemType::Callback {
3040
function: typefn,
@@ -45,7 +55,7 @@ fn dir(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &
4555
let time = crate::fs::BiosTime();
4656
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
4757
// Open the first partition
48-
let volume = mgr.open_volume(VolumeIdx(0))?;
58+
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
4959
let root_dir = mgr.open_root_dir(volume)?;
5060
let mut total_bytes = 0u64;
5161
let mut num_files = 0;
@@ -112,14 +122,49 @@ fn load(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &m
112122
}
113123
}
114124

125+
/// Called when the "exec" command is executed.
126+
fn exec(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
127+
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), embedded_sdmmc::Error<bios::Error>> {
128+
let bios_block = crate::fs::BiosBlock();
129+
let time = crate::fs::BiosTime();
130+
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
131+
// Open the first partition
132+
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
133+
let root_dir = mgr.open_root_dir(volume)?;
134+
let file = mgr.open_file_in_dir(root_dir, filename, embedded_sdmmc::Mode::ReadOnly)?;
135+
let buffer = ctx.tpa.as_slice_u8();
136+
let count = mgr.read(file, buffer)?;
137+
if count != mgr.file_length(file)? as usize {
138+
osprintln!("File too large! Max {} bytes allowed.", buffer.len());
139+
return Ok(());
140+
}
141+
let Ok(s) = core::str::from_utf8(&buffer[0..count]) else {
142+
osprintln!("File is not valid UTF-8");
143+
return Ok(());
144+
};
145+
// tell the main loop to run from these bytes next
146+
ctx.exec_tpa = Some(s.len());
147+
Ok(())
148+
}
149+
150+
// index can't panic - we always have enough args
151+
let r = work(ctx, args[0]);
152+
match r {
153+
Ok(_) => {}
154+
Err(e) => {
155+
osprintln!("Error: {:?}", e);
156+
}
157+
}
158+
}
159+
115160
/// Called when the "type" command is executed.
116161
fn typefn(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
117162
fn work(ctx: &mut Ctx, filename: &str) -> Result<(), embedded_sdmmc::Error<bios::Error>> {
118163
let bios_block = crate::fs::BiosBlock();
119164
let time = crate::fs::BiosTime();
120165
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
121166
// Open the first partition
122-
let volume = mgr.open_volume(VolumeIdx(0))?;
167+
let volume = mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?;
123168
let root_dir = mgr.open_root_dir(volume)?;
124169
let file = mgr.open_file_in_dir(root_dir, filename, embedded_sdmmc::Mode::ReadOnly)?;
125170
let buffer = ctx.tpa.as_slice_u8();

src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
2727
&ram::RUN_ITEM,
2828
&ram::LOAD_ITEM,
2929
&fs::LOAD_ITEM,
30+
&fs::EXEC_ITEM,
3031
&fs::TYPE_ITEM,
3132
&screen::CLS_ITEM,
3233
&screen::MODE_ITEM,

src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ impl StdInput {
325325
pub struct Ctx {
326326
config: config::Config,
327327
tpa: program::TransientProgramArea,
328+
/// This flag is set if the "run" command is entered. It tells us
329+
/// to take our input bytes from the TPA.
330+
exec_tpa: Option<usize>,
328331
}
329332

330333
impl core::fmt::Write for Ctx {
@@ -441,6 +444,7 @@ pub extern "C" fn os_main(api: &bios::Api) -> ! {
441444
// We have to trust the values given to us by the BIOS. If it lies, we will crash.
442445
program::TransientProgramArea::new(tpa_start, tpa_size)
443446
},
447+
exec_tpa: None,
444448
};
445449

446450
osprintln!(
@@ -461,6 +465,35 @@ pub extern "C" fn os_main(api: &bios::Api) -> ! {
461465
for b in &buffer[0..count] {
462466
menu.input_byte(*b);
463467
}
468+
// TODO: Consider recursively executing scripts, so that scripts can
469+
// call scripts.
470+
if let Some(n) = menu.context.exec_tpa {
471+
menu.context.exec_tpa = None;
472+
let ptr = menu.context.tpa.steal_top(n);
473+
osprintln!("\rExecuting TPA...");
474+
let mut has_chars = false;
475+
let slice = unsafe { core::slice::from_raw_parts(ptr, n) };
476+
// TODO: Give the user some way to break out of the loop.
477+
for b in slice {
478+
// Files contain `\n` or `\r\n` line endings.
479+
// menu wants `\r` line endings.
480+
if *b == b'\n' {
481+
if has_chars {
482+
// Execute this line
483+
menu.input_byte(b'\r');
484+
has_chars = false;
485+
}
486+
} else if *b == b'\r' {
487+
// Drop carriage returns
488+
} else {
489+
menu.input_byte(*b);
490+
has_chars = true;
491+
}
492+
}
493+
unsafe {
494+
menu.context.tpa.restore_top(n);
495+
}
496+
}
464497
(api.power_idle)();
465498
}
466499
}

src/program.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,25 +177,24 @@ impl TransientProgramArea {
177177

178178
/// Borrow the TPA region as a slice of words
179179
pub fn as_slice_u32(&mut self) -> &mut [u32] {
180-
unsafe {
181-
core::slice::from_raw_parts_mut(
182-
self.memory_bottom,
183-
self.memory_top.offset_from(self.memory_bottom) as usize,
184-
)
185-
}
180+
unsafe { core::slice::from_raw_parts_mut(self.memory_bottom, self.size_words()) }
186181
}
187182

188183
/// Borrow the TPA region as a slice of bytes
189184
pub fn as_slice_u8(&mut self) -> &mut [u8] {
190185
unsafe {
191186
core::slice::from_raw_parts_mut(
192187
self.memory_bottom as *mut u8,
193-
(self.memory_top.offset_from(self.memory_bottom) as usize)
194-
* core::mem::size_of::<u32>(),
188+
self.size_words() * core::mem::size_of::<u32>(),
195189
)
196190
}
197191
}
198192

193+
/// Size of the TPA in 32-bit words
194+
fn size_words(&self) -> usize {
195+
unsafe { self.memory_top.offset_from(self.memory_bottom) as usize }
196+
}
197+
199198
/// Loads a program from disk into the Transient Program Area.
200199
///
201200
/// The program must be in the Neotron Executable format.
@@ -270,6 +269,38 @@ impl TransientProgramArea {
270269
self.last_entry = 0;
271270
Ok(result)
272271
}
272+
273+
/// Move data to the top of TPA and make TPA shorter.
274+
///
275+
/// Moves `size` bytes to the top of the TPA, and then pretends the TPA is
276+
/// `size` bytes shorter than it was.
277+
///
278+
/// `size` will be rounded up to a multiple of 4.
279+
///
280+
/// Panics if `n` is too big to fit in the TPA.
281+
///
282+
/// Returns a pointer to the data that now sits outside of the TPA. There
283+
/// will be `size` bytes at this address but you must manage the lifetimes
284+
/// yourself.
285+
pub fn steal_top(&mut self, size: usize) -> *const u8 {
286+
let stolen_words = (size + 3) / 4;
287+
if stolen_words >= self.size_words() {
288+
panic!("Stole too much from TPA!");
289+
}
290+
unsafe {
291+
// Top goes down to free memory above it
292+
let new_top = self.memory_top.sub(stolen_words);
293+
// Copy the data from the bottom to above the newly reduced TPA
294+
core::ptr::copy(self.memory_bottom, new_top, stolen_words);
295+
new_top as *mut u8
296+
}
297+
}
298+
299+
/// Restore the TPA back where it was.
300+
pub unsafe fn restore_top(&mut self, size: usize) {
301+
let restored_words = (size + 3) / 4;
302+
self.memory_top = self.memory_top.add(restored_words);
303+
}
273304
}
274305

275306
/// Application API to print things to the console.

0 commit comments

Comments
 (0)