From be26b9e4d5aa57db211daa3b1b5c163fdb032b88 Mon Sep 17 00:00:00 2001 From: Monadic Cat Date: Mon, 25 Apr 2022 15:24:16 -0500 Subject: [PATCH] add new syscalls; cross platform abstraction for io and rng seeding --- Cargo.toml | 16 +++++ src/io.rs | 144 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 19 ++++-- src/rand.rs | 21 +++++++ src/syscalls/mod.rs | 10 ++- 5 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 src/io.rs create mode 100644 src/rand.rs diff --git a/Cargo.toml b/Cargo.toml index 8cab3f3..9eb3378 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,19 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +once_cell = { version = "1.10.0", default-features = false, features = ["std"], optional = true } +rand_core = { version = "0.6", default-features = false, optional = true } +rand_pcg = { version = "0.3.1", default-features = false, optional = true } + +[target.'cfg(not(ableOS))'.dependencies] +rand_core = { version = "0.6", default-features = false, optional = true, features = ["getrandom"] } + + +[features] +default = ["rng", "pcg_rng", "io"] +# Controls availability of the `rand` module, which provides seeding for `rand` (the crate) RNGs. +rng = ["dep:rand_core"] +# Controls the availability of `obtain_rng`, which internally uses `rand_pcg`. +pcg_rng = ["rng", "dep:rand_pcg"] +# Controls the availability of the cross platform `io` module. +io = ["dep:once_cell"] diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..a77d1fe --- /dev/null +++ b/src/io.rs @@ -0,0 +1,144 @@ +//! This module provides cross platform I/O facilities. It takes inspiration from the Rust `std::io` module, +//! and uses that for platform support outside of ableOS. +//! One day, we might hope for this module to be superseded by real `std` support for ableOS. +use ::std::io::{self, Read, BufRead, BufReader, IoSliceMut}; + +/// Print a string to the console. +fn print(x: &str) { + #[cfg(ableOS)] { + extern "C" { + fn print_char(x: u32); + } + fn print_byte(x: u8) { + unsafe { print_char(x as u32) } + } + for b in x.bytes() { + print_byte(b); + } + } + #[cfg(not(ableOS))] { + print!("{}", x) + } +} +/// Like [`print`], but starts a new line after. +pub fn println(x: &str) { + print(x); + print("\n"); +} + +/// Like [`println`], but meant to print to a standard error output. +pub fn eprintln(x: &str) { + #[cfg(ableOS)] { + println(x); + } + #[cfg(not(ableOS))] { + eprintln!("{}", x) + } +} + +#[cfg(ableOS)] +static STDIN: ::once_cell::sync::Lazy<::std::sync::Mutex>> = ::once_cell::sync::Lazy::new(|| { + ::std::sync::Mutex::new(BufReader::new(AbleStdinRaw)) +}); + +#[cfg(ableOS)] +struct AbleStdinRaw; +#[cfg(ableOS)] +impl Read for AbleStdinRaw { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // TODO: detect EOF + loop { + let chr = unsafe { crate::syscalls::get_input() }; + if chr != 0 { + break Ok(char::from_u32(chr as u32).unwrap().encode_utf8(buf).len()) + } + } + } +} + +/// A handle to the standard input stream of the process. +/// See [`Stdin`](::std::io::Stdin) for roughly how this should be treated. +pub struct Stdin { + #[cfg(ableOS)] + inner: &'static ::std::sync::Mutex>, + #[cfg(not(ableOS))] + inner: io::Stdin, +} +impl Stdin { + pub fn lock(&self) -> StdinLock<'_> { + #[cfg(ableOS)] { + StdinLock { + inner: self.inner.lock().unwrap() + } + } + #[cfg(not(ableOS))] { + StdinLock { + inner: self.inner.lock(), + } + } + } +} + +/// A locked reference to the [`Stdin`] handle. +/// See [`StdinLock`](::std::io::StdinLock) for roughly how this should be treated. +pub struct StdinLock<'a> { + #[cfg(ableOS)] + inner: ::std::sync::MutexGuard<'a, BufReader>, + #[cfg(not(ableOS))] + inner: io::StdinLock<'a>, +} + +impl<'a> BufRead for StdinLock<'_> { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + self.inner.fill_buf() + } + + fn consume(&mut self, n: usize) { + self.inner.consume(n) + } + + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + self.inner.read_until(byte, buf) + } + + fn read_line(&mut self, buf: &mut String) -> io::Result { + self.inner.read_line(buf) + } +} +impl Read for StdinLock<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.inner.read_vectored(bufs) + } + + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.inner.read_to_end(buf) + } + + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + self.inner.read_to_string(buf) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.inner.read_exact(buf) + } +} + +/// Constructs a new handle to the standard input of the current process. +/// Each handle returned is a reference to a shared global buffer whose access is synchronized via a mutex. +/// If you need more explicit control over locking, see the [`Stdin::lock`] method. +pub fn stdin() -> Stdin { + #[cfg(ableOS)] { + Stdin { + inner: &STDIN + } + } + #[cfg(not(ableOS))] { + Stdin { + inner: ::std::io::stdin(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 46b24bf..c4493fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,22 @@ -#![no_std] +//! The base crate for writing guest programs to run on ableOS. +//! +//! ableOS is not a target known to rustc, and so we rely on a general `--cfg` switch +//! to control the use of ableOS target specific code when underlying the portable +//! abstractions defined in this crate. +//! +//! To target ableOS, set `cfg(ableOS)` and `--target=wasm32-unknown-unknown`. +//! ```text +//! RUSTFLAGS='--cfg=ableOS' cargo build --target=wasm32-unknown-unknown +//! ``` #[macro_use] pub mod logger; pub mod driver; pub mod process; pub mod syscalls; +#[cfg(feature = "io")] +pub mod io; +#[cfg(feature = "rng")] +pub mod rand; pub use core::*; - -extern "C" { - pub fn get_random() -> u32; -} diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..8f80478 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,21 @@ +use ::rand_core::SeedableRng; + +/// Seed an RNG with system provided randomness. +pub fn seed_rng() -> R { + #[cfg(ableOS)] { + let seed = unsafe { + let seed_a = crate::syscalls::get_random(); + let seed_b = crate::syscalls::get_random(); + (seed_a as u64) << 32 | seed_b as u64 + }; + R::seed_from_u64(seed) + } + #[cfg(not(ableOS))] { + R::from_entropy() + } +} + +#[cfg(feature = "pcg_rng")] +pub fn obtain_rng() -> impl ::rand_core::RngCore { + seed_rng::<::rand_pcg::Pcg32>() +} diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs index e618ae8..fb952f2 100644 --- a/src/syscalls/mod.rs +++ b/src/syscalls/mod.rs @@ -1,12 +1,11 @@ #![deny(missing_docs)] -//! The module of syscalls. +//! This module provides declarations for all host provided functions. use crate::process::{signals::Signals, PID}; pub mod file_calls; pub mod time_calls; -#[no_mangle] /// All system calls are defined here. extern "C" { @@ -21,4 +20,11 @@ extern "C" { /// A temporary function to test the syscall interface pub fn add(a: u32, b: u32) -> u32; + // TODO: ascertain whether the ableOS host provides randomness good enough for CSPRNGs + /// The ableOS host provides entropy usable for seeding PRNGs. + pub fn get_random() -> u32; + + // This currently has very x86 specific semantics. + /// Pops a byte from the key buffer. + pub fn get_input() -> i32; }