implemented 'type' and 'wait' builtins
fixed some tcsetpgrp() misbehavior fixed not being able to redirect stderr from builtins
This commit is contained in:
@@ -5,6 +5,7 @@ use ariadne::Color;
|
||||
use ariadne::{Report, ReportKind};
|
||||
use rand::TryRng;
|
||||
|
||||
use crate::procio::RedirGuard;
|
||||
use crate::{
|
||||
libsh::term::{Style, Styled},
|
||||
parse::lex::{Span, SpanSource},
|
||||
@@ -44,7 +45,7 @@ impl ColorRng {
|
||||
|
||||
pub fn last_color(&mut self) -> Color {
|
||||
if let Some(color) = self.last_color.take() {
|
||||
return color;
|
||||
color
|
||||
} else {
|
||||
let color = self.next().unwrap_or(Color::White);
|
||||
self.last_color = Some(color);
|
||||
@@ -78,6 +79,10 @@ pub fn last_color() -> Color {
|
||||
COLOR_RNG.with(|rng| rng.borrow_mut().last_color())
|
||||
}
|
||||
|
||||
pub fn clear_color() {
|
||||
COLOR_RNG.with(|rng| rng.borrow_mut().last_color = None);
|
||||
}
|
||||
|
||||
pub trait ShResultExt {
|
||||
fn blame(self, span: Span) -> Self;
|
||||
fn try_blame(self, span: Span) -> Self;
|
||||
@@ -154,15 +159,25 @@ pub struct ShErr {
|
||||
src_span: Option<Span>,
|
||||
labels: Vec<ariadne::Label<Span>>,
|
||||
sources: Vec<SpanSource>,
|
||||
notes: Vec<String>
|
||||
notes: Vec<String>,
|
||||
|
||||
/// If we propagate through a redirect boundary, we take ownership of
|
||||
/// the RedirGuard(s) so that redirections stay alive until the error
|
||||
/// is printed. Multiple guards can accumulate as the error bubbles
|
||||
/// through nested redirect scopes.
|
||||
io_guards: Vec<RedirGuard>
|
||||
}
|
||||
|
||||
impl ShErr {
|
||||
pub fn new(kind: ShErrKind, span: Span) -> Self {
|
||||
Self { kind, src_span: Some(span), labels: vec![], sources: vec![], notes: vec![] }
|
||||
Self { kind, src_span: Some(span), labels: vec![], sources: vec![], notes: vec![], io_guards: vec![] }
|
||||
}
|
||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()], io_guards: vec![] }
|
||||
}
|
||||
pub fn with_redirs(mut self, guard: RedirGuard) -> Self {
|
||||
self.io_guards.push(guard);
|
||||
self
|
||||
}
|
||||
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
||||
let color = last_color(); // use last_color to ensure the same color is used for the label and the message given
|
||||
@@ -178,12 +193,12 @@ impl ShErr {
|
||||
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||
}
|
||||
pub fn blame(self, span: Span) -> Self {
|
||||
let ShErr { kind, src_span: _, labels, sources, notes } = self;
|
||||
Self { kind, src_span: Some(span), labels, sources, notes }
|
||||
let ShErr { kind, src_span: _, labels, sources, notes, io_guards } = self;
|
||||
Self { kind, src_span: Some(span), labels, sources, notes, io_guards }
|
||||
}
|
||||
pub fn try_blame(self, span: Span) -> Self {
|
||||
match self {
|
||||
ShErr { kind, src_span: None, labels, sources, notes } => Self { kind, src_span: Some(span), labels, sources, notes },
|
||||
ShErr { kind, src_span: None, labels, sources, notes, io_guards } => Self { kind, src_span: Some(span), labels, sources, notes, io_guards },
|
||||
_ => self
|
||||
}
|
||||
}
|
||||
@@ -197,23 +212,23 @@ impl ShErr {
|
||||
self
|
||||
}
|
||||
pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> Self {
|
||||
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
||||
let ShErr { kind, src_span, mut labels, mut sources, notes, io_guards } = self;
|
||||
sources.push(source);
|
||||
labels.push(label);
|
||||
Self { kind, src_span, labels, sources, notes }
|
||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
||||
}
|
||||
pub fn with_context(self, ctx: VecDeque<(SpanSource, ariadne::Label<Span>)>) -> Self {
|
||||
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
||||
let ShErr { kind, src_span, mut labels, mut sources, notes, io_guards } = self;
|
||||
for (src, label) in ctx {
|
||||
sources.push(src);
|
||||
labels.push(label);
|
||||
}
|
||||
Self { kind, src_span, labels, sources, notes }
|
||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
||||
}
|
||||
pub fn with_note(self, note: impl Into<String>) -> Self {
|
||||
let ShErr { kind, src_span, labels, sources, mut notes } = self;
|
||||
let ShErr { kind, src_span, labels, sources, mut notes, io_guards } = self;
|
||||
notes.push(note.into());
|
||||
Self { kind, src_span, labels, sources, notes }
|
||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
||||
}
|
||||
pub fn build_report(&self) -> Option<Report<'_, Span>> {
|
||||
let span = self.src_span.as_ref()?;
|
||||
@@ -313,8 +328,7 @@ pub enum ShErrKind {
|
||||
ResourceLimitExceeded,
|
||||
BadPermission,
|
||||
Errno(Errno),
|
||||
FileNotFound,
|
||||
CmdNotFound,
|
||||
NotFound,
|
||||
ReadlineErr,
|
||||
|
||||
// Not really errors, more like internal signals
|
||||
@@ -339,8 +353,7 @@ impl Display for ShErrKind {
|
||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||
Self::BadPermission => "Bad Permissions",
|
||||
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
||||
Self::FileNotFound => "File not found",
|
||||
Self::CmdNotFound => "Command not found",
|
||||
Self::NotFound => "Not Found",
|
||||
Self::CleanExit(_) => "",
|
||||
Self::FuncReturn(_) => "Syntax Error",
|
||||
Self::LoopContinue(_) => "Syntax Error",
|
||||
|
||||
215
src/libsh/guards.rs
Normal file
215
src/libsh/guards.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::os::fd::{BorrowedFd, RawFd};
|
||||
|
||||
use nix::sys::termios::{self, LocalFlags, Termios, tcgetattr, tcsetattr};
|
||||
use nix::unistd::isatty;
|
||||
use scopeguard::guard;
|
||||
|
||||
thread_local! {
|
||||
static ORIG_TERMIOS: RefCell<Option<Termios>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
use crate::parse::lex::Span;
|
||||
use crate::procio::{IoFrame, borrow_fd};
|
||||
use crate::readline::term::get_win_size;
|
||||
use crate::state::write_vars;
|
||||
|
||||
use super::sys::TTY_FILENO;
|
||||
|
||||
// ============================================================================
|
||||
// ScopeGuard — RAII variable scope management
|
||||
// ============================================================================
|
||||
|
||||
pub fn scope_guard(args: Option<Vec<(String, Span)>>) -> impl Drop {
|
||||
let argv = args.map(|a| a.into_iter().map(|(s, _)| s).collect::<Vec<_>>());
|
||||
write_vars(|v| v.descend(argv));
|
||||
guard((), |_| {
|
||||
write_vars(|v| v.ascend());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shared_scope_guard() -> impl Drop {
|
||||
write_vars(|v| v.descend(None));
|
||||
guard((), |_| {
|
||||
write_vars(|v| v.ascend());
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VarCtxGuard — RAII variable context cleanup
|
||||
// ============================================================================
|
||||
|
||||
pub fn var_ctx_guard(
|
||||
vars: HashSet<String>,
|
||||
) -> scopeguard::ScopeGuard<HashSet<String>, impl FnOnce(HashSet<String>)> {
|
||||
guard(vars, |vars| {
|
||||
write_vars(|v| {
|
||||
for var in &vars {
|
||||
v.unset_var(var).ok();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RedirGuard — RAII I/O redirection restoration
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RedirGuard(pub(crate) IoFrame);
|
||||
|
||||
impl RedirGuard {
|
||||
pub(crate) fn new(frame: IoFrame) -> Self {
|
||||
Self(frame)
|
||||
}
|
||||
pub fn persist(mut self) {
|
||||
use nix::unistd::close;
|
||||
if let Some(saved) = self.0.saved_io.take() {
|
||||
close(saved.0).ok();
|
||||
close(saved.1).ok();
|
||||
close(saved.2).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RedirGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.restore().ok();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RawModeGuard — RAII terminal raw mode management
|
||||
// ============================================================================
|
||||
|
||||
pub fn raw_mode() -> RawModeGuard {
|
||||
let orig = termios::tcgetattr(unsafe { BorrowedFd::borrow_raw(*TTY_FILENO) })
|
||||
.expect("Failed to get terminal attributes");
|
||||
let mut raw = orig.clone();
|
||||
termios::cfmakeraw(&mut raw);
|
||||
// Keep ISIG enabled so Ctrl+C/Ctrl+Z still generate signals
|
||||
raw.local_flags |= termios::LocalFlags::ISIG;
|
||||
// Keep OPOST enabled so \n is translated to \r\n on output
|
||||
raw.output_flags |= termios::OutputFlags::OPOST;
|
||||
termios::tcsetattr(
|
||||
unsafe { BorrowedFd::borrow_raw(*TTY_FILENO) },
|
||||
termios::SetArg::TCSANOW,
|
||||
&raw,
|
||||
)
|
||||
.expect("Failed to set terminal to raw mode");
|
||||
|
||||
let (_cols, _rows) = get_win_size(*TTY_FILENO);
|
||||
|
||||
ORIG_TERMIOS.with(|cell| *cell.borrow_mut() = Some(orig.clone()));
|
||||
|
||||
RawModeGuard {
|
||||
orig,
|
||||
fd: *TTY_FILENO,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawModeGuard {
|
||||
orig: termios::Termios,
|
||||
fd: RawFd,
|
||||
}
|
||||
|
||||
impl RawModeGuard {
|
||||
/// Disable raw mode temporarily for a specific operation
|
||||
pub fn disable_for<F: FnOnce() -> R, R>(&self, func: F) -> R {
|
||||
unsafe {
|
||||
let fd = BorrowedFd::borrow_raw(self.fd);
|
||||
// Temporarily restore the original termios
|
||||
termios::tcsetattr(fd, termios::SetArg::TCSANOW, &self.orig)
|
||||
.expect("Failed to temporarily disable raw mode");
|
||||
|
||||
// Run the function
|
||||
let result = func();
|
||||
|
||||
// Re-enable raw mode
|
||||
let mut raw = self.orig.clone();
|
||||
termios::cfmakeraw(&mut raw);
|
||||
// Keep ISIG enabled so Ctrl+C/Ctrl+Z still generate signals
|
||||
raw.local_flags |= termios::LocalFlags::ISIG;
|
||||
// Keep OPOST enabled so \n is translated to \r\n on output
|
||||
raw.output_flags |= termios::OutputFlags::OPOST;
|
||||
termios::tcsetattr(fd, termios::SetArg::TCSANOW, &raw).expect("Failed to re-enable raw mode");
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cooked_mode<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
let current = tcgetattr(borrow_fd(*TTY_FILENO)).expect("Failed to get terminal attributes");
|
||||
let orig = ORIG_TERMIOS.with(|cell| cell.borrow().clone())
|
||||
.expect("with_cooked_mode called before raw_mode()");
|
||||
tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, &orig)
|
||||
.expect("Failed to restore cooked mode");
|
||||
let res = f();
|
||||
tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, ¤t)
|
||||
.expect("Failed to restore raw mode");
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RawModeGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = termios::tcsetattr(
|
||||
BorrowedFd::borrow_raw(self.fd),
|
||||
termios::SetArg::TCSANOW,
|
||||
&self.orig,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TermiosGuard — RAII termios state management
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TermiosGuard {
|
||||
saved_termios: Option<Termios>,
|
||||
}
|
||||
|
||||
impl TermiosGuard {
|
||||
pub fn new(new_termios: Termios) -> Self {
|
||||
let mut new = Self {
|
||||
saved_termios: None,
|
||||
};
|
||||
|
||||
if isatty(*TTY_FILENO).unwrap() {
|
||||
let current_termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
new.saved_termios = Some(current_termios);
|
||||
|
||||
termios::tcsetattr(
|
||||
std::io::stdin(),
|
||||
nix::sys::termios::SetArg::TCSANOW,
|
||||
&new_termios,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TermiosGuard {
|
||||
fn default() -> Self {
|
||||
let mut termios_val = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios_val.local_flags &= !LocalFlags::ECHOCTL;
|
||||
Self::new(termios_val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TermiosGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(saved) = &self.saved_termios {
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, saved).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod error;
|
||||
pub mod flog;
|
||||
pub mod guards;
|
||||
pub mod sys;
|
||||
pub mod term;
|
||||
pub mod utils;
|
||||
|
||||
@@ -1,52 +1,7 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use termios::{LocalFlags, Termios};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
||||
open("/dev/tty", OFlag::O_RDWR, Mode::empty()).expect("Failed to open /dev/tty")
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TermiosGuard {
|
||||
saved_termios: Option<Termios>,
|
||||
}
|
||||
|
||||
impl TermiosGuard {
|
||||
pub fn new(new_termios: Termios) -> Self {
|
||||
let mut new = Self {
|
||||
saved_termios: None,
|
||||
};
|
||||
|
||||
if isatty(*TTY_FILENO).unwrap() {
|
||||
let current_termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
new.saved_termios = Some(current_termios);
|
||||
|
||||
termios::tcsetattr(
|
||||
std::io::stdin(),
|
||||
nix::sys::termios::SetArg::TCSANOW,
|
||||
&new_termios,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TermiosGuard {
|
||||
fn default() -> Self {
|
||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||
Self::new(termios)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TermiosGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(saved) = &self.saved_termios {
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, saved).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user