shell: better readline, rsh: allow builtin pubkey

This commit is contained in:
Mark Poliakov 2025-01-03 19:06:16 +02:00
parent f36436ee07
commit 89f4965460
19 changed files with 296 additions and 82 deletions

View File

@ -18,7 +18,7 @@ pub struct DebugOptions {
impl Default for DebugOptions {
fn default() -> Self {
Self {
serial_level: LogLevel::Info,
serial_level: LogLevel::Debug,
display_level: LogLevel::Info,
disable_program_trace: false,
}

2
userspace/Cargo.lock generated
View File

@ -290,6 +290,7 @@ name = "cross"
version = "0.1.0"
dependencies = [
"libc",
"yggdrasil-rt",
]
[[package]]
@ -1323,6 +1324,7 @@ name = "shell"
version = "0.1.0"
dependencies = [
"clap",
"cross",
"libc",
"nom",
"thiserror",

View File

@ -5,6 +5,9 @@ edition = "2021"
[dependencies]
[target.'cfg(target_os = "yggdrasil")'.dependencies]
yggdrasil-rt.workspace = true
[target.'cfg(unix)'.dependencies]
libc = "*"

View File

@ -1,12 +1,14 @@
use std::{
io::{self, Read, Write},
io::{self, Read, Stdin, Write},
ops::{Deref, DerefMut},
os::fd::{AsRawFd, RawFd},
process::Stdio,
time::Duration,
};
use crate::sys::{
self, PidFd as SysPidFd, Pipe as SysPipe, Poll as SysPoll, TimerFd as SysTimerFd,
self, PidFd as SysPidFd, Pipe as SysPipe, Poll as SysPoll, RawStdin as SysRawStdin,
TimerFd as SysTimerFd,
};
use self::sys::PipeImpl;
@ -23,6 +25,9 @@ pub struct PidFd(sys::PidFdImpl);
#[repr(transparent)]
pub struct Pipe(sys::PipeImpl);
#[repr(transparent)]
pub struct RawStdin<'a>(sys::RawStdinImpl<'a>);
impl Poll {
pub fn new() -> io::Result<Self> {
sys::PollImpl::new().map(Self)
@ -115,3 +120,29 @@ impl AsRawFd for Pipe {
self.0.as_raw_fd()
}
}
impl<'a> RawStdin<'a> {
pub fn new(stdin: &'a mut Stdin) -> io::Result<Self> {
sys::RawStdinImpl::new(stdin).map(Self)
}
}
impl Deref for RawStdin<'_> {
type Target = Stdin;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl DerefMut for RawStdin<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.deref_mut()
}
}
impl AsRawFd for RawStdin<'_> {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}

View File

@ -9,7 +9,8 @@ mod unix;
pub(crate) use unix::*;
use std::{
io::{self, Read, Write},
io::{self, Read, Stdin, Write},
ops::DerefMut,
os::fd::{AsRawFd, RawFd},
process::Stdio,
time::Duration,
@ -37,3 +38,7 @@ pub(crate) trait Pipe: Read + Write + AsRawFd + Sized {
fn new(read_nonblocking: bool, write_nonblocking: bool) -> io::Result<(Self, Self)>;
fn to_child_stdio(&self) -> Stdio;
}
pub(crate) trait RawStdin<'a>: Sized + DerefMut<Target = Stdin> + 'a {
fn new(stdin: &'a mut Stdin) -> io::Result<Self>;
}

View File

@ -2,8 +2,10 @@ pub mod poll;
pub mod timer;
pub mod pid;
pub mod pipe;
pub mod term;
pub use poll::PollImpl;
pub use timer::TimerFdImpl;
pub use pid::PidFdImpl;
pub use pipe::PipeImpl;
pub use term::RawStdinImpl;

View File

@ -0,0 +1,54 @@
use std::{
io::{self, Stdin},
mem::MaybeUninit,
ops::{Deref, DerefMut},
os::fd::AsRawFd,
};
use crate::sys::RawStdin;
pub struct RawStdinImpl<'a> {
inner: &'a mut Stdin,
saved: libc::termios,
}
impl<'a> RawStdin<'a> for RawStdinImpl<'a> {
fn new(stdin: &'a mut Stdin) -> io::Result<Self> {
let mut saved = MaybeUninit::uninit();
if unsafe { libc::tcgetattr(stdin.as_raw_fd(), saved.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let saved = unsafe { saved.assume_init() };
let mut new = saved;
unsafe { libc::cfmakeraw(&mut new) }
if unsafe { libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Self {
inner: stdin,
saved,
})
}
}
impl Drop for RawStdinImpl<'_> {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(self.inner.as_raw_fd(), libc::TCSANOW, &self.saved);
}
}
}
impl Deref for RawStdinImpl<'_> {
type Target = Stdin;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl DerefMut for RawStdinImpl<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

View File

@ -2,8 +2,10 @@ pub mod poll;
pub mod timer;
pub mod pid;
pub mod pipe;
pub mod term;
pub use poll::PollImpl;
pub use timer::TimerFdImpl;
pub use pid::PidFdImpl;
pub use pipe::PipeImpl;
pub use term::RawStdinImpl;

View File

@ -0,0 +1,47 @@
use std::{
io::{self, Stdin},
ops::{Deref, DerefMut},
os::{
fd::AsRawFd,
yggdrasil::io::terminal::{update_terminal_options, TerminalOptions},
},
};
use crate::sys::RawStdin;
pub struct RawStdinImpl<'a> {
inner: &'a mut Stdin,
saved: TerminalOptions,
}
impl<'a> RawStdin<'a> for RawStdinImpl<'a> {
fn new(stdin: &'a mut Stdin) -> io::Result<Self> {
let saved = unsafe {
update_terminal_options(stdin.as_raw_fd(), |_| TerminalOptions::raw_input())
}?;
Ok(Self {
inner: stdin,
saved,
})
}
}
impl Drop for RawStdinImpl<'_> {
fn drop(&mut self) {
unsafe { update_terminal_options(self.inner.as_raw_fd(), |_| self.saved).ok() };
}
}
impl Deref for RawStdinImpl<'_> {
type Target = Stdin;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl DerefMut for RawStdinImpl<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

View File

@ -10,3 +10,6 @@ thiserror.workspace = true
[target.'cfg(unix)'.dependencies]
libc = "0.2.150"
[lints]
workspace = true

View File

@ -6,14 +6,14 @@ use std::{
pub use self::{input::ReadChar, sys::RawMode};
#[cfg(target_os = "yggdrasil")]
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
mod yggdrasil;
#[cfg(target_os = "yggdrasil")]
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
use yggdrasil as sys;
#[cfg(unix)]
#[cfg(any(unix, rust_analyzer))]
mod unix;
#[cfg(unix)]
#[cfg(any(unix, rust_analyzer))]
use unix as sys;
mod input;

View File

@ -1,5 +1,5 @@
use std::{
io::{self, Stdin, Stdout},
io::{self, Stdout},
mem::MaybeUninit,
os::fd::AsRawFd,
};
@ -9,7 +9,7 @@ pub struct RawMode {
}
impl RawMode {
pub unsafe fn enter(stdin: &Stdin) -> Result<Self, io::Error> {
pub unsafe fn enter<F: AsRawFd>(stdin: &F) -> Result<Self, io::Error> {
let mut old = MaybeUninit::uninit();
if libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) != 0 {
@ -39,7 +39,7 @@ impl RawMode {
Ok(Self { saved_termios: old })
}
pub unsafe fn leave(&self, stdin: &Stdin) {
pub unsafe fn leave<F: AsRawFd>(&self, stdin: &F) {
libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios);
}
}

View File

@ -22,3 +22,6 @@ log.workspace = true
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil-rng_core-0.6.4" }
aes = { version = "0.8.4" }
env_logger = "0.11.5"
[lints]
workspace = true

View File

@ -1,8 +1,7 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
#![feature(if_let_guard)]
use std::{
collections::HashSet, net::SocketAddr, path::PathBuf, process::ExitCode, str::FromStr,
time::Duration,
collections::HashSet, net::SocketAddr, os::fd::{self, IntoRawFd}, path::PathBuf, process::ExitCode, str::FromStr, time::Duration
};
use clap::Parser;
@ -63,14 +62,17 @@ impl rsh::server::Session for YggdrasilSession {
},
)?;
let pty_slave_fd = pty_slave.as_raw_fd();
let pty_slave_stdin = pty_slave.into_raw_fd();
let pty_slave_stdout = fd::clone_fd(pty_slave_stdin)?;
let pty_slave_stderr = fd::clone_fd(pty_slave_stdin)?;
let group_id = yggdrasil::process::create_process_group();
let shell = unsafe {
Command::new("/bin/sh")
.arg("-l")
.stdin(Stdio::from_raw_fd(pty_slave_fd))
.stdout(Stdio::from_raw_fd(pty_slave_fd))
.stderr(Stdio::from_raw_fd(pty_slave_fd))
.stdin(Stdio::from_raw_fd(pty_slave_stdin))
.stdout(Stdio::from_raw_fd(pty_slave_stdout))
.stderr(Stdio::from_raw_fd(pty_slave_stderr))
.process_group(group_id)
.gain_terminal(0)
.spawn()?
@ -129,9 +131,9 @@ impl rsh::server::Session for YggdrasilSession {
}
}
#[cfg(unix)]
#[cfg(any(unix, rust_analyzer))]
pub type SessionImpl = rsh::server::EchoSession;
#[cfg(target_os = "yggdrasil")]
#[cfg(any(target_os = "yggdrasil", rust_analyzer))]
pub type SessionImpl = YggdrasilSession;
#[derive(Debug, thiserror::Error)]
@ -144,7 +146,7 @@ fn run(args: Args) -> Result<(), Error> {
let keystore = SimpleServerKeyStore {
path: args.keystore,
accepted_keys: HashSet::from_iter([
"ed25519 sha256 63:f4:df:41:c3:ec:a7:7c:ea:f1:65:22:91:8b:14:60:8f:ec:cd:19:90:2e:12:33:66:e3:33:24:96:3d:63:c3".into()
"ed25519 sha256 ab:5d:d8:a9:56:79:f9:0f:47:32:a4:4b:f5:fb:49:2d:3d:91:a7:7d:70:60:8a:57:84:36:43:80:7e:91:8a:c3".into()
]),
};
let server_config = ServerConfig::with_default_algorithms(keystore);

View File

@ -5,6 +5,8 @@ edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
cross.workspace = true
clap.workspace = true
thiserror.workspace = true

View File

@ -15,6 +15,7 @@ use parser::Command;
mod builtins;
mod parser;
mod readline;
mod sys;
#[derive(Debug, thiserror::Error)]
@ -68,16 +69,27 @@ impl Outcome {
}
impl Input {
pub fn getline(&mut self, buf: &mut String) -> io::Result<usize> {
pub fn getline(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
match self {
Self::Interactive(input) => {
let mut stdout = stdout();
print!("$ ");
stdout.flush().ok();
input.read_line(buf)
readline::readline(input, &mut stdout, buf, |stdout| {
let cwd = env::current_dir();
let cwd = match cwd {
Ok(cwd) => format!("{}", cwd.display()),
Err(_) => "???".into(),
};
let prompt = format!("{cwd} $ ");
stdout.write_all(prompt.as_bytes()).ok();
})
}
Self::File(input) => {
let mut string = String::new();
input.read_line(&mut string)?;
buf[..string.len()].copy_from_slice(string.as_bytes());
Ok(string.len())
}
Self::File(input) => input.read_line(buf),
}
}
@ -177,15 +189,16 @@ pub fn exec(
Ok(status)
}
fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitCode> {
let mut line = String::new();
fn run(mut input: Input, vars: &mut HashMap<String, String>) -> Result<ExitCode, Error> {
// let mut line = String::new();
let mut line = [0; 4096];
if input.is_interactive() {
sys::init_signal_handler();
}
let code = loop {
line.clear();
// line.clear();
let len = input.getline(&mut line)?;
@ -193,6 +206,9 @@ fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitC
break ExitCode::SUCCESS;
}
let Ok(line) = std::str::from_utf8(&line[..len]) else {
continue;
};
let line = line.trim();
let line = match line.split_once('#') {
Some((line, _)) => line.trim(),
@ -233,12 +249,12 @@ fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitC
Ok(code)
}
fn run_file<P: AsRef<Path>>(path: P, env: &mut HashMap<String, String>) -> io::Result<ExitCode> {
fn run_file<P: AsRef<Path>>(path: P, env: &mut HashMap<String, String>) -> Result<ExitCode, Error> {
let input = BufReader::new(File::open(path)?);
run(Input::File(input), env)
}
fn run_stdin(env: &mut HashMap<String, String>) -> io::Result<ExitCode> {
fn run_stdin(env: &mut HashMap<String, String>) -> Result<ExitCode, Error> {
run(Input::Interactive(stdin()), env)
}
@ -267,8 +283,8 @@ fn main() -> ExitCode {
match result {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("{:?}", e);
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}

View File

@ -73,7 +73,7 @@ fn parse_command(_env: &HashMap<String, String>, input: &[Token]) -> Result<Comm
let mut current = vec![];
let mut it = input.into_iter();
let mut it = input.iter();
while let Some(token) = it.next() {
match token {
&Token::Word(word) => {
@ -85,7 +85,7 @@ fn parse_command(_env: &HashMap<String, String>, input: &[Token]) -> Result<Comm
}
elements.push(PipelineElement {
words: mem::replace(&mut current, vec![]),
words: mem::take(&mut current)
});
}
Token::Output(1) => {

View File

@ -0,0 +1,85 @@
use std::io::{Read, Stdin, Stdout, Write};
use cross::io::RawStdin;
use crate::Error;
enum Outcome {
Interrupt,
Data(usize),
}
fn readline_inner(stdin: &mut RawStdin, stdout: &mut Stdout, buffer: &mut [u8]) -> Result<Outcome, Error> {
let mut pos = 0;
let mut ch = [0];
while pos < buffer.len() {
let len = stdin.read(&mut ch)?;
if len == 0 {
break;
}
let ch = ch[0];
match ch {
// ^D
0x04 => {
if pos == 0 {
break;
} else {
continue;
}
}
// ^C
0x03 => return Ok(Outcome::Interrupt),
// TODO completion
b'\t' => (),
0x7F => {
if pos != 0 {
stdout.write_all(b"\x1B[D \x1B[D").ok();
stdout.flush().ok();
pos -= 1;
}
}
ch if ch.is_ascii_graphic() || ch.is_ascii_whitespace() => {
stdout.write_all(&[ch]).ok();
stdout.flush().ok();
buffer[pos] = ch;
pos += 1;
}
_ => (),
}
if ch == b'\n' || ch == b'\r' {
if ch == b'\r' {
stdout.write_all(b"\n").ok();
stdout.flush().ok();
}
break;
}
}
Ok(Outcome::Data(pos))
}
pub fn readline<P: Fn(&mut Stdout)>(
stdin: &mut Stdin,
stdout: &mut Stdout,
buffer: &mut [u8],
prompt: P
) -> Result<usize, Error> {
let mut stdin = RawStdin::new(stdin)?;
loop {
prompt(stdout);
stdout.flush().ok();
match readline_inner(&mut stdin, stdout, buffer)? {
Outcome::Data(n) => break Ok(n),
Outcome::Interrupt => {
stdout.write_all(b"\r\n").ok();
},
}
}
}

View File

@ -1,11 +1,10 @@
use std::{
collections::HashMap,
io,
os::{
fd::{FromRawFd, OwnedFd, RawFd},
unix::process::{CommandExt, ExitStatusExt},
fd::{FromRawFd, OwnedFd},
unix::process::ExitStatusExt,
},
process::{Child, Command, ExitStatus, Stdio},
process::{Command, ExitStatus},
};
use crate::{Outcome, Pipeline, SpawnedPipeline};
@ -15,30 +14,6 @@ pub struct Pipe {
pub write: OwnedFd,
}
pub fn exec_binary(
interactive: bool,
binary: &str,
args: &[String],
env: &HashMap<String, String>,
input: Stdio,
output: Stdio,
) -> Result<Child, io::Error> {
let mut command = Command::new(binary);
let mut command = command
.args(args)
.envs(env.iter())
.stdin(input)
.stdout(output);
if interactive {
unsafe {
command = command.process_group(0);
}
}
command.spawn()
}
pub fn create_pipe() -> Result<Pipe, io::Error> {
let mut fds = [0; 2];
let (read, write) = unsafe {
@ -67,7 +42,7 @@ impl From<ExitStatus> for Outcome {
pub fn init_signal_handler() {}
pub fn spawn_pipeline(
interactive: bool,
_interactive: bool,
pipeline: Pipeline<'_>,
) -> Result<SpawnedPipeline, io::Error> {
let mut children = vec![];
@ -86,7 +61,7 @@ pub fn spawn_pipeline(
}
pub fn wait_for_pipeline(
interactive: bool,
_interactive: bool,
mut pipeline: SpawnedPipeline,
) -> Result<Outcome, io::Error> {
for mut child in pipeline.children.drain(..) {
@ -94,22 +69,4 @@ pub fn wait_for_pipeline(
}
Ok(Outcome::ok())
// let self_group_id = process::group_id();
// for mut child in pipeline.children.drain(..) {
// let status = child.wait()?;
// if !status.success() {
// if interactive {
// set_terminal_group(self_group_id).ok();
// }
// return Ok(Outcome::from(status));
// }
// }
// if interactive {
// set_terminal_group(self_group_id).ok();
// }
// Ok(Outcome::ok())
}