implemented 'type' and 'wait' builtins
fixed some tcsetpgrp() misbehavior fixed not being able to redirect stderr from builtins
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -533,6 +533,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
@@ -596,6 +602,7 @@ dependencies = [
|
|||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
|
"scopeguard",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ log = "0.4.29"
|
|||||||
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
||||||
rand = "0.10.0"
|
rand = "0.10.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
scopeguard = "1.2.0"
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, read_logic, write_logic},
|
state::{self, read_logic, write_logic},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn alias(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -20,8 +17,8 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -46,14 +43,14 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
let Some((name, body)) = arg.split_once('=') else {
|
let Some((name, body)) = arg.split_once('=') else {
|
||||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args"));
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args"));
|
||||||
};
|
};
|
||||||
write_logic(|l| l.insert_alias(name, body));
|
write_logic(|l| l.insert_alias(name, body, span.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn unalias(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -62,8 +59,8 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use ariadne::Span;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, read_vars, write_vars}
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, procio::borrow_fd, state::{self, VarFlags, VarKind, write_vars}
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
|
||||||
|
|
||||||
fn arr_op_optspec() -> Vec<OptSpec> {
|
fn arr_op_optspec() -> Vec<OptSpec> {
|
||||||
vec![
|
vec![
|
||||||
OptSpec {
|
OptSpec {
|
||||||
@@ -44,7 +40,7 @@ impl Default for ArrOpOpts {
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum End { Front, Back }
|
enum End { Front, Back }
|
||||||
|
|
||||||
fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -55,8 +51,8 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
|
|
||||||
@@ -85,7 +81,7 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -97,8 +93,8 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let _arr_op_opts = get_arr_op_opts(opts)?;
|
let _arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
let Some((name, _)) = argv.next() else {
|
let Some((name, _)) = argv.next() else {
|
||||||
@@ -124,23 +120,23 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_pop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn arr_pop(node: Node) -> ShResult<()> {
|
||||||
arr_pop_inner(node, io_stack, job, End::Back)
|
arr_pop_inner(node, End::Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_fpop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn arr_fpop(node: Node) -> ShResult<()> {
|
||||||
arr_pop_inner(node, io_stack, job, End::Front)
|
arr_pop_inner(node, End::Front)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_push(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn arr_push(node: Node) -> ShResult<()> {
|
||||||
arr_push_inner(node, io_stack, job, End::Back)
|
arr_push_inner(node, End::Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_fpush(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn arr_fpush(node: Node) -> ShResult<()> {
|
||||||
arr_push_inner(node, io_stack, job, End::Front)
|
arr_push_inner(node, End::Front)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_rotate(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn arr_rotate(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -151,8 +147,8 @@ pub fn arr_rotate(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShRe
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
for (arg, _) in argv {
|
for (arg, _) in argv {
|
||||||
write_vars(|v| -> ShResult<()> {
|
write_vars(|v| -> ShResult<()> {
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ use ariadne::Fmt;
|
|||||||
use yansi::Color;
|
use yansi::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
state::{self},
|
state::{self},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn cd(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let span = node.get_span();
|
let span = node.get_span();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -22,8 +19,8 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
};
|
};
|
||||||
let cd_span = argv.first().unwrap().span.clone();
|
let cd_span = argv.first().unwrap().span.clone();
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
|
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
|
||||||
(PathBuf::from(arg),Some(span))
|
(PathBuf::from(arg),Some(span))
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ use bitflags::bitflags;
|
|||||||
use nix::{libc::STDOUT_FILENO, unistd::write};
|
use nix::{libc::STDOUT_FILENO, unistd::write};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
readline::complete::{BashCompSpec, CompContext, CompSpec},
|
readline::complete::{BashCompSpec, CompContext, CompSpec},
|
||||||
state::{self, read_meta, write_meta},
|
state::{self, read_meta, write_meta},
|
||||||
};
|
};
|
||||||
@@ -149,7 +147,7 @@ pub struct CompOpts {
|
|||||||
pub opt_flags: CompOptFlags,
|
pub opt_flags: CompOptFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn complete_builtin(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -168,8 +166,8 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
|
||||||
let comp_opts = get_comp_opts(opts)?;
|
let comp_opts = get_comp_opts(opts)?;
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if comp_opts.flags.contains(CompFlags::PRINT) {
|
if comp_opts.flags.contains(CompFlags::PRINT) {
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
@@ -219,7 +217,7 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compgen_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn compgen_builtin(node: Node) -> ShResult<()> {
|
||||||
let _blame = node.get_span().clone();
|
let _blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -239,7 +237,6 @@ pub fn compgen_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) ->
|
|||||||
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
||||||
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
||||||
let comp_opts = get_comp_opts(opts)?;
|
let comp_opts = get_comp_opts(opts)?;
|
||||||
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
|
||||||
|
|
||||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
|||||||
use yansi::Color;
|
use yansi::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||||
parse::{NdRule, Node, lex::Span},
|
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, read_meta, write_meta},
|
state::{self, read_meta, write_meta},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,7 +101,7 @@ fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn pushd(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -113,8 +111,8 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let mut dir = None;
|
let mut dir = None;
|
||||||
let mut rotate_idx = None;
|
let mut rotate_idx = None;
|
||||||
@@ -184,7 +182,7 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn popd(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -194,8 +192,8 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let mut remove_idx = None;
|
let mut remove_idx = None;
|
||||||
let mut no_cd = false;
|
let mut no_cd = false;
|
||||||
@@ -276,7 +274,7 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn dirs(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -286,8 +284,8 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let mut abbreviate_home = true;
|
let mut abbreviate_home = true;
|
||||||
let mut one_per_line = false;
|
let mut one_per_line = false;
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
expand::expand_prompt,
|
expand::expand_prompt,
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +37,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn echo(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -51,8 +49,8 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
|
||||||
let flags = get_echo_flags(opts).blame(blame)?;
|
let flags = get_echo_flags(opts).blame(blame)?;
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||||
borrow_fd(STDERR_FILENO)
|
borrow_fd(STDERR_FILENO)
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node, execute::exec_input},
|
parse::{NdRule, Node, execute::{exec_input, prepare_argv}},
|
||||||
procio::IoStack,
|
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn eval(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -16,8 +13,8 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut expanded_argv = prepare_argv(argv)?;
|
||||||
let expanded_argv = expanded_argv.unwrap();
|
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
|
||||||
|
|
||||||
if expanded_argv.is_empty() {
|
if expanded_argv.is_empty() {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
@@ -30,5 +27,5 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
exec_input(joined_argv, None, false)
|
exec_input(joined_argv, None, false, Some("eval".into()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use nix::{errno::Errno, unistd::execvpe};
|
use nix::{errno::Errno, unistd::execvpe};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node, execute::ExecArgs},
|
parse::{NdRule, Node, execute::{ExecArgs, prepare_argv}},
|
||||||
procio::IoStack,
|
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn exec_builtin(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -18,13 +15,8 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut expanded_argv = prepare_argv(argv)?;
|
||||||
let expanded_argv = expanded_argv.unwrap();
|
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
|
||||||
if let Some(g) = guard {
|
|
||||||
// Persist redirections so they affect the entire shell,
|
|
||||||
// not just this command call
|
|
||||||
g.persist()
|
|
||||||
}
|
|
||||||
|
|
||||||
if expanded_argv.is_empty() {
|
if expanded_argv.is_empty() {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
@@ -42,7 +34,7 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => Err(
|
Errno::ENOENT => Err(
|
||||||
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
ShErr::new(ShErrKind::NotFound, span.clone())
|
||||||
.labeled(span, format!("exec: command not found: {}", cmd_str))
|
.labeled(span, format!("exec: command not found: {}", cmd_str))
|
||||||
),
|
),
|
||||||
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
|
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
|
||||||
|
|||||||
73
src/builtin/intro.rs
Normal file
73
src/builtin/intro.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use std::{env, os::unix::fs::PermissionsExt, path::Path};
|
||||||
|
|
||||||
|
use ariadne::{Fmt, Span};
|
||||||
|
|
||||||
|
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind, ShResult, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::KEYWORDS}, state::{self, ShAlias, ShFunc, read_logic, read_vars}};
|
||||||
|
|
||||||
|
pub fn type_builtin(node: Node) -> ShResult<()> {
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut argv = prepare_argv(argv)?;
|
||||||
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* we have to check in the same order that the dispatcher checks this
|
||||||
|
* 1. function
|
||||||
|
* 2. builtin
|
||||||
|
* 3. command
|
||||||
|
*/
|
||||||
|
|
||||||
|
'outer: for (arg,span) in argv {
|
||||||
|
if let Some(func) = read_logic(|v| v.get_func(&arg)) {
|
||||||
|
let ShFunc { body: _, source } = func;
|
||||||
|
let (line, col) = source.line_and_col();
|
||||||
|
let name = source.source().name();
|
||||||
|
println!("{arg} is a function defined at {name}:{}:{}", line + 1, col + 1);
|
||||||
|
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
|
||||||
|
let ShAlias { body, source } = alias;
|
||||||
|
let (line, col) = source.line_and_col();
|
||||||
|
let name = source.source().name();
|
||||||
|
println!("{arg} is an alias for '{body}' defined at {name}:{}:{}", line + 1, col + 1);
|
||||||
|
} else if BUILTINS.contains(&arg.as_str()) {
|
||||||
|
println!("{arg} is a shell builtin");
|
||||||
|
} else if KEYWORDS.contains(&arg.as_str()) {
|
||||||
|
println!("{arg} is a shell keyword");
|
||||||
|
} else {
|
||||||
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
|
let paths = path.split(':')
|
||||||
|
.map(Path::new)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
if let Ok(entries) = path.read_dir() {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||||
|
|
||||||
|
if meta.is_file()
|
||||||
|
&& is_exec
|
||||||
|
&& let Some(name) = entry.file_name().to_str()
|
||||||
|
&& name == arg {
|
||||||
|
println!("{arg} is {}", entry.path().display());
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(1);
|
||||||
|
return Err(ShErr::at(ShErrKind::NotFound, span, format!("'{}' is not a command, function, or alias", arg.fg(next_color()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{JobBldr, JobCmdFlags, JobID},
|
jobs::{JobCmdFlags, JobID, wait_bg},
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||||
parse::{NdRule, Node, lex::Span},
|
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, read_jobs, write_jobs},
|
state::{self, read_jobs, write_jobs},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
|
||||||
|
|
||||||
pub enum JobBehavior {
|
pub enum JobBehavior {
|
||||||
Foregound,
|
Foregound,
|
||||||
Background,
|
Background,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
|
pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let cmd_tk = node.get_command();
|
let cmd_tk = node.get_command();
|
||||||
let cmd_span = cmd_tk.unwrap().span.clone();
|
let cmd_span = cmd_tk.unwrap().span.clone();
|
||||||
@@ -32,8 +30,8 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if read_jobs(|j| j.get_fg().is_some()) {
|
if read_jobs(|j| j.get_fg().is_some()) {
|
||||||
@@ -84,7 +82,12 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
if arg.starts_with('%') {
|
if arg.starts_with('%') {
|
||||||
let arg = arg.strip_prefix('%').unwrap();
|
let arg = arg.strip_prefix('%').unwrap();
|
||||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
Ok(arg.parse::<usize>().unwrap())
|
let num = arg.parse::<usize>().unwrap_or_default();
|
||||||
|
if num == 0 {
|
||||||
|
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid job id: {}", arg.fg(next_color()))))
|
||||||
|
} else {
|
||||||
|
Ok(num.saturating_sub(1))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = write_jobs(|j| {
|
let result = write_jobs(|j| {
|
||||||
let query_result = j.query(JobID::Command(arg.into()));
|
let query_result = j.query(JobID::Command(arg.into()));
|
||||||
@@ -119,7 +122,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn jobs(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -128,8 +131,8 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
@@ -158,7 +161,45 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn wait(node: Node) -> ShResult<()> {
|
||||||
|
let blame = node.get_span().clone();
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv,
|
||||||
|
} = node.class
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut argv = prepare_argv(argv)?;
|
||||||
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
if read_jobs(|j| j.curr_job().is_none()) {
|
||||||
|
state::set_status(0);
|
||||||
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
|
||||||
|
}
|
||||||
|
let argv = argv.into_iter()
|
||||||
|
.map(|arg| {
|
||||||
|
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
|
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
|
||||||
|
} else {
|
||||||
|
Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<ShResult<Vec<JobID>>>()?;
|
||||||
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
write_jobs(|j| j.wait_all_bg())?;
|
||||||
|
} else {
|
||||||
|
for arg in argv {
|
||||||
|
wait_bg(arg)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't set status here, the status of the waited-on job should be the status of the wait builtin
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disown(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -168,8 +209,8 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
|||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::borrow_fd, state::{self, read_vars, write_vars}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -195,8 +195,6 @@ impl MapNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::setup_builtin;
|
|
||||||
|
|
||||||
fn map_opts_spec() -> [OptSpec; 6] {
|
fn map_opts_spec() -> [OptSpec; 6] {
|
||||||
[
|
[
|
||||||
OptSpec {
|
OptSpec {
|
||||||
@@ -243,7 +241,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn map(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -254,7 +252,6 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
|
|
||||||
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||||
let map_opts = get_map_opts(opts);
|
let map_opts = get_map_opts(opts);
|
||||||
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
|
||||||
if !argv.is_empty() {
|
if !argv.is_empty() {
|
||||||
argv.remove(0); // remove "map" command from argv
|
argv.remove(0); // remove "map" command from argv
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
use nix::unistd::Pid;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{ChildProc, JobBldr},
|
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{
|
|
||||||
Redir,
|
|
||||||
execute::prepare_argv,
|
|
||||||
lex::{Span, Tk},
|
|
||||||
},
|
|
||||||
procio::{IoStack, RedirGuard},
|
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,77 +23,15 @@ pub mod varcmds;
|
|||||||
pub mod zoltraak;
|
pub mod zoltraak;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod arrops;
|
pub mod arrops;
|
||||||
|
pub mod intro;
|
||||||
|
|
||||||
pub const BUILTINS: [&str; 41] = [
|
pub const BUILTINS: [&str; 43] = [
|
||||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
||||||
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
||||||
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
||||||
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate"
|
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
|
||||||
///
|
|
||||||
/// Prepares a builtin for execution by processing arguments, setting up
|
|
||||||
/// redirections, and registering the command as a child process in the given
|
|
||||||
/// `JobBldr`
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// * argv - The vector of raw argument tokens
|
|
||||||
/// * job - A mutable reference to a `JobBldr`
|
|
||||||
/// * io_mode - An optional 2-tuple consisting of a mutable reference to an
|
|
||||||
/// `IoStack` and a vector of `Redirs`
|
|
||||||
///
|
|
||||||
/// # Behavior
|
|
||||||
/// * Cleans, expands, and word splits the arg vector
|
|
||||||
/// * Adds a new `ChildProc` to the job builder
|
|
||||||
/// * Performs redirections, if any.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// * The processed arg vector
|
|
||||||
/// * The popped `IoFrame`, if any
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
/// * If redirections are given to this function, the caller must call
|
|
||||||
/// `IoFrame.restore()` on the returned `IoFrame`
|
|
||||||
/// * If redirections are given, the second field of the resulting tuple will
|
|
||||||
/// *always* be `Some()`
|
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
|
||||||
type SetupReturns = ShResult<(Option<Vec<(String, Span)>>, Option<RedirGuard>)>;
|
|
||||||
pub fn setup_builtin(
|
|
||||||
argv: Option<Vec<Tk>>,
|
|
||||||
job: &mut JobBldr,
|
|
||||||
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
|
||||||
) -> SetupReturns {
|
|
||||||
let mut argv = argv.map(|argv| prepare_argv(argv)).transpose()?;
|
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let cmd_name = argv
|
|
||||||
.as_mut()
|
|
||||||
.and_then(|argv| {
|
|
||||||
if argv.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(argv.remove(0).0)
|
|
||||||
}
|
|
||||||
}).unwrap_or_else(|| String::new());
|
|
||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let guard = io_mode.map(|(io,rdrs)| {
|
|
||||||
io.append_to_frame(rdrs);
|
|
||||||
io.pop_frame().redirect()
|
|
||||||
}).transpose()?;
|
|
||||||
|
|
||||||
// We return the io_frame because the caller needs to also call
|
|
||||||
// io_frame.restore()
|
|
||||||
Ok((argv, guard))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn true_builtin() -> ShResult<()> {
|
pub fn true_builtin() -> ShResult<()> {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn pwd(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv: _,
|
||||||
} = node.class
|
} = node.class
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ use nix::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
readline::term::RawModeGuard,
|
readline::term::RawModeGuard,
|
||||||
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
||||||
};
|
};
|
||||||
@@ -63,7 +61,7 @@ pub struct ReadOpts {
|
|||||||
flags: ReadFlags,
|
flags: ReadFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn read_builtin(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -75,8 +73,8 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
|
||||||
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, None).blame(blame.clone())?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if let Some(prompt) = read_opts.prompt {
|
if let Some(prompt) = read_opts.prompt {
|
||||||
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
state::{self, write_vars},
|
state::{self, write_vars},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn shift(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -16,8 +13,8 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if let Some((arg, span)) = argv.next() {
|
if let Some((arg, span)) = argv.next() {
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShResult, ShResultExt},
|
libsh::error::{ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, write_shopts},
|
state::{self, write_shopts},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn shopt(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -18,8 +15,8 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
let mut output = write_shopts(|s| s.display_opts())?;
|
let mut output = write_shopts(|s| s.display_opts())?;
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
state::{self, source_file},
|
state::{self, source_file},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn source(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -17,8 +14,8 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ use nix::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, read_logic, write_logic},
|
state::{self, read_logic, write_logic},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +112,7 @@ impl Display for TrapTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn trap(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -123,8 +121,8 @@ pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node, lex::split_tk_at},
|
parse::{NdRule, Node, execute::prepare_argv, lex::split_tk_at},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
pub fn readonly(node: Node) -> ShResult<()> {
|
||||||
|
|
||||||
pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -18,8 +15,6 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
|
||||||
|
|
||||||
// Remove "readonly" from argv
|
// Remove "readonly" from argv
|
||||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
@@ -61,7 +56,7 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn unset(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
@@ -71,8 +66,8 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument"));
|
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument"));
|
||||||
@@ -89,7 +84,7 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn export(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -98,8 +93,6 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
|
||||||
|
|
||||||
// Remove "export" from argv
|
// Remove "export" from argv
|
||||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
@@ -134,7 +127,7 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn local(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -143,8 +136,6 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
|
||||||
|
|
||||||
// Remove "local" from argv
|
// Remove "local" from argv
|
||||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ use std::os::unix::fs::OpenOptionsExt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||||
jobs::JobBldr,
|
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, execute::prepare_argv},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::borrow_fd,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
||||||
struct ZoltFlags: u32 {
|
struct ZoltFlags: u32 {
|
||||||
@@ -29,7 +26,7 @@ bitflags! {
|
|||||||
/// The file given as an argument is completely destroyed. The command works by
|
/// The file given as an argument is completely destroyed. The command works by
|
||||||
/// shredding all of the data contained in the file, before truncating the
|
/// shredding all of the data contained in the file, before truncating the
|
||||||
/// length of the file to 0 to ensure that not even any metadata remains.
|
/// length of the file to 0 to ensure that not even any metadata remains.
|
||||||
pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn zoltraak(node: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -106,8 +103,8 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
let argv = argv.unwrap();
|
if !argv.is_empty() { argv.remove(0); }
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
||||||
|
|||||||
@@ -856,7 +856,7 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
|
|||||||
let mut io_stack = IoStack::new();
|
let mut io_stack = IoStack::new();
|
||||||
io_stack.push_frame(io_frame);
|
io_stack.push_frame(io_frame);
|
||||||
|
|
||||||
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false, Some("process_sub".into())) {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -887,7 +887,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
io_stack.push_frame(cmd_sub_io_frame);
|
io_stack.push_frame(cmd_sub_io_frame);
|
||||||
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false, Some("command_sub".into())) {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
unsafe { libc::_exit(1) };
|
unsafe { libc::_exit(1) };
|
||||||
}
|
}
|
||||||
@@ -2092,7 +2092,7 @@ pub fn expand_aliases(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(alias) = log_tab.get_alias(&raw_tk) {
|
if let Some(alias) = log_tab.get_alias(&raw_tk) {
|
||||||
result.replace_range(tk.span.range(), &alias);
|
result.replace_range(tk.span.range(), &alias.to_string());
|
||||||
expanded_this_iter.push(raw_tk);
|
expanded_this_iter.push(raw_tk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/jobs.rs
145
src/jobs.rs
@@ -1,12 +1,15 @@
|
|||||||
|
use scopeguard::defer;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
libsh::{
|
||||||
error::ShResult,
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
sys::TTY_FILENO,
|
||||||
term::{Style, Styled},
|
term::{Style, Styled},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoMode, borrow_fd},
|
procio::{IoMode, borrow_fd},
|
||||||
signal::{disable_reaping, enable_reaping},
|
signal::{disable_reaping, enable_reaping},
|
||||||
state::{self, set_status, write_jobs},
|
state::{self, ShellParam, set_status, write_jobs, write_vars},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||||
@@ -55,6 +58,21 @@ impl fmt::Display for DisplayWaitStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn code_from_status(stat: &WtStat) -> Option<i32> {
|
||||||
|
match stat {
|
||||||
|
WtStat::Exited(_, exit_code) => {
|
||||||
|
Some(*exit_code)
|
||||||
|
}
|
||||||
|
WtStat::Stopped(_, sig) => {
|
||||||
|
Some(SIG_EXIT_OFFSET + *sig as i32)
|
||||||
|
}
|
||||||
|
WtStat::Signaled(_, sig, _) => {
|
||||||
|
Some(SIG_EXIT_OFFSET + *sig as i32)
|
||||||
|
}
|
||||||
|
_ => { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum JobID {
|
pub enum JobID {
|
||||||
Pgid(Pid),
|
Pgid(Pid),
|
||||||
@@ -200,7 +218,7 @@ impl JobTab {
|
|||||||
}
|
}
|
||||||
fn prune_jobs(&mut self) {
|
fn prune_jobs(&mut self) {
|
||||||
while let Some(job) = self.jobs.last() {
|
while let Some(job) = self.jobs.last() {
|
||||||
if job.is_none() {
|
if job.is_none() || job.as_ref().unwrap().is_done() {
|
||||||
self.jobs.pop();
|
self.jobs.pop();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -215,6 +233,7 @@ impl JobTab {
|
|||||||
self.next_open_pos()
|
self.next_open_pos()
|
||||||
};
|
};
|
||||||
job.set_tabid(tab_pos);
|
job.set_tabid(tab_pos);
|
||||||
|
let last_pid = job.children().last().map(|c| c.pid());
|
||||||
self.order.push(tab_pos);
|
self.order.push(tab_pos);
|
||||||
if !silent {
|
if !silent {
|
||||||
write(
|
write(
|
||||||
@@ -227,6 +246,11 @@ impl JobTab {
|
|||||||
} else {
|
} else {
|
||||||
self.jobs[tab_pos] = Some(job);
|
self.jobs[tab_pos] = Some(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(pid) = last_pid {
|
||||||
|
write_vars(|v| v.set_param(ShellParam::LastJob, &pid.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
Ok(tab_pos)
|
Ok(tab_pos)
|
||||||
}
|
}
|
||||||
pub fn order(&self) -> &[usize] {
|
pub fn order(&self) -> &[usize] {
|
||||||
@@ -257,6 +281,25 @@ impl JobTab {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
|
||||||
|
let Some(job) = self.query_mut(id.clone()) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
match id {
|
||||||
|
JobID::Pid(pid) => {
|
||||||
|
let Some(child) = job.children_mut().iter_mut().find(|c| c.pid() == pid) else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
child.set_stat(stat);
|
||||||
|
}
|
||||||
|
JobID::Pgid(_) |
|
||||||
|
JobID::TableID(_) |
|
||||||
|
JobID::Command(_) => {
|
||||||
|
job.set_stats(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||||
match identifier {
|
match identifier {
|
||||||
// Match by process group ID
|
// Match by process group ID
|
||||||
@@ -315,6 +358,17 @@ impl JobTab {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn wait_all_bg(&mut self) -> ShResult<()> {
|
||||||
|
disable_reaping();
|
||||||
|
defer! {
|
||||||
|
enable_reaping();
|
||||||
|
}
|
||||||
|
for job in self.jobs.iter_mut() {
|
||||||
|
let Some(job) = job else { continue };
|
||||||
|
job.wait_pgrp()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||||
if let Some(tabid) = tabid {
|
if let Some(tabid) = tabid {
|
||||||
@@ -560,6 +614,12 @@ impl Job {
|
|||||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||||
&mut self.children
|
&mut self.children
|
||||||
}
|
}
|
||||||
|
pub fn is_done(&self) -> bool {
|
||||||
|
self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.all(|chld| chld.exited() || chld.stat() == WtStat::Signaled(chld.pid(), Signal::SIGHUP, true))
|
||||||
|
}
|
||||||
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
||||||
let stat = match sig {
|
let stat = match sig {
|
||||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||||
@@ -653,7 +713,9 @@ impl Job {
|
|||||||
let padding_count = symbol.len() + id.to_string().len() + 3;
|
let padding_count = symbol.len() + id.to_string().len() + 3;
|
||||||
let padding = " ".repeat(padding_count);
|
let padding = " ".repeat(padding_count);
|
||||||
|
|
||||||
let mut output = format!("[{}]{}\t", id + 1, symbol);
|
let mut output = String::new();
|
||||||
|
let id_box = format!("[{}]{}", id + 1, symbol);
|
||||||
|
output.push_str(&format!("{id_box}\t"));
|
||||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||||
let pid = if pids || init {
|
let pid = if pids || init {
|
||||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||||
@@ -676,8 +738,12 @@ impl Job {
|
|||||||
},
|
},
|
||||||
_ => stat_line.styled(Style::Cyan),
|
_ => stat_line.styled(Style::Cyan),
|
||||||
};
|
};
|
||||||
|
if i != 0 {
|
||||||
|
let padding = " ".repeat(id_box.len() - 1);
|
||||||
|
stat_line = format!("{padding}{}", stat_line);
|
||||||
|
}
|
||||||
if i != self.get_cmds().len() - 1 {
|
if i != self.get_cmds().len() - 1 {
|
||||||
stat_line = format!("{} |", stat_line);
|
stat_line.push_str(" |");
|
||||||
}
|
}
|
||||||
|
|
||||||
let stat_final = if long {
|
let stat_final = if long {
|
||||||
@@ -698,7 +764,7 @@ impl Job {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn term_ctlr() -> Pid {
|
pub fn term_ctlr() -> Pid {
|
||||||
tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp())
|
tcgetpgrp(borrow_fd(*TTY_FILENO)).unwrap_or(getpgrp())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls attach_tty() on the shell's process group to retake control of the
|
/// Calls attach_tty() on the shell's process group to retake control of the
|
||||||
@@ -712,6 +778,55 @@ pub fn take_term() -> ShResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wait_bg(id: JobID) -> ShResult<()> {
|
||||||
|
disable_reaping();
|
||||||
|
defer! {
|
||||||
|
enable_reaping();
|
||||||
|
};
|
||||||
|
match id {
|
||||||
|
JobID::Pid(pid) => {
|
||||||
|
let stat = loop {
|
||||||
|
match waitpid(pid, None) {
|
||||||
|
Ok(stat) => break stat,
|
||||||
|
Err(Errno::EINTR) => continue, // Retry on signal interruption
|
||||||
|
Err(Errno::ECHILD) => return Ok(()), // No such child, treat as already reaped
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
write_jobs(|j| j.update_by_id(id, stat))?;
|
||||||
|
set_status(code_from_status(&stat).unwrap_or(0));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let Some(mut job) = write_jobs(|j| j.remove_job(id.clone())) else {
|
||||||
|
return Err(ShErr::simple(ShErrKind::ExecFail, format!("wait: No such job with id {:?}", id)));
|
||||||
|
};
|
||||||
|
let statuses = job.wait_pgrp()?;
|
||||||
|
let mut was_stopped = false;
|
||||||
|
let mut code = 0;
|
||||||
|
for status in statuses {
|
||||||
|
code = code_from_status(&status).unwrap_or(0);
|
||||||
|
match status {
|
||||||
|
WtStat::Stopped(_, _) => {
|
||||||
|
was_stopped = true;
|
||||||
|
}
|
||||||
|
WtStat::Signaled(_, sig, _) => {
|
||||||
|
if sig == Signal::SIGTSTP {
|
||||||
|
was_stopped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if was_stopped {
|
||||||
|
write_jobs(|j| j.insert_job(job, false))?;
|
||||||
|
}
|
||||||
|
set_status(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Waits on the current foreground job and updates the shell's last status code
|
/// Waits on the current foreground job and updates the shell's last status code
|
||||||
pub fn wait_fg(job: Job) -> ShResult<()> {
|
pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||||
if job.children().is_empty() {
|
if job.children().is_empty() {
|
||||||
@@ -721,23 +836,22 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
let mut was_stopped = false;
|
let mut was_stopped = false;
|
||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
disable_reaping();
|
disable_reaping();
|
||||||
|
defer! {
|
||||||
|
enable_reaping();
|
||||||
|
}
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
|
code = code_from_status(&status).unwrap_or(0);
|
||||||
match status {
|
match status {
|
||||||
WtStat::Exited(_, exit_code) => {
|
WtStat::Stopped(_, _) => {
|
||||||
code = exit_code;
|
|
||||||
}
|
|
||||||
WtStat::Stopped(_, sig) => {
|
|
||||||
was_stopped = true;
|
was_stopped = true;
|
||||||
write_jobs(|j| j.fg_to_bg(status))?;
|
write_jobs(|j| j.fg_to_bg(status))?;
|
||||||
code = SIG_EXIT_OFFSET + sig as i32;
|
|
||||||
}
|
}
|
||||||
WtStat::Signaled(_, sig, _) => {
|
WtStat::Signaled(_, sig, _) => {
|
||||||
if sig == Signal::SIGTSTP {
|
if sig == Signal::SIGTSTP {
|
||||||
was_stopped = true;
|
was_stopped = true;
|
||||||
write_jobs(|j| j.fg_to_bg(status))?;
|
write_jobs(|j| j.fg_to_bg(status))?;
|
||||||
}
|
}
|
||||||
code = SIG_EXIT_OFFSET + sig as i32;
|
|
||||||
}
|
}
|
||||||
_ => { /* Do nothing */ }
|
_ => { /* Do nothing */ }
|
||||||
}
|
}
|
||||||
@@ -750,7 +864,6 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
take_term()?;
|
take_term()?;
|
||||||
set_status(code);
|
set_status(code);
|
||||||
enable_reaping();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -766,7 +879,7 @@ pub fn dispatch_job(job: Job, is_bg: bool) -> ShResult<()> {
|
|||||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||||
// If we aren't attached to a terminal, the pgid already controls it, or the
|
// If we aren't attached to a terminal, the pgid already controls it, or the
|
||||||
// process group does not exist Then return ok
|
// process group does not exist Then return ok
|
||||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
if !isatty(*TTY_FILENO).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +896,7 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
|||||||
|
|
||||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&new_mask), Some(&mut mask_bkup))?;
|
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&new_mask), Some(&mut mask_bkup))?;
|
||||||
|
|
||||||
let result = tcsetpgrp(borrow_fd(0), pgid);
|
let result = tcsetpgrp(borrow_fd(*TTY_FILENO), pgid);
|
||||||
|
|
||||||
pthread_sigmask(
|
pthread_sigmask(
|
||||||
SigmaskHow::SIG_SETMASK,
|
SigmaskHow::SIG_SETMASK,
|
||||||
@@ -794,7 +907,7 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
|||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
tcsetpgrp(borrow_fd(0), getpgrp())?;
|
tcsetpgrp(borrow_fd(*TTY_FILENO), getpgrp())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use ariadne::Color;
|
|||||||
use ariadne::{Report, ReportKind};
|
use ariadne::{Report, ReportKind};
|
||||||
use rand::TryRng;
|
use rand::TryRng;
|
||||||
|
|
||||||
|
use crate::procio::RedirGuard;
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::term::{Style, Styled},
|
libsh::term::{Style, Styled},
|
||||||
parse::lex::{Span, SpanSource},
|
parse::lex::{Span, SpanSource},
|
||||||
@@ -44,7 +45,7 @@ impl ColorRng {
|
|||||||
|
|
||||||
pub fn last_color(&mut self) -> Color {
|
pub fn last_color(&mut self) -> Color {
|
||||||
if let Some(color) = self.last_color.take() {
|
if let Some(color) = self.last_color.take() {
|
||||||
return color;
|
color
|
||||||
} else {
|
} else {
|
||||||
let color = self.next().unwrap_or(Color::White);
|
let color = self.next().unwrap_or(Color::White);
|
||||||
self.last_color = Some(color);
|
self.last_color = Some(color);
|
||||||
@@ -78,6 +79,10 @@ pub fn last_color() -> Color {
|
|||||||
COLOR_RNG.with(|rng| rng.borrow_mut().last_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 {
|
pub trait ShResultExt {
|
||||||
fn blame(self, span: Span) -> Self;
|
fn blame(self, span: Span) -> Self;
|
||||||
fn try_blame(self, span: Span) -> Self;
|
fn try_blame(self, span: Span) -> Self;
|
||||||
@@ -154,15 +159,25 @@ pub struct ShErr {
|
|||||||
src_span: Option<Span>,
|
src_span: Option<Span>,
|
||||||
labels: Vec<ariadne::Label<Span>>,
|
labels: Vec<ariadne::Label<Span>>,
|
||||||
sources: Vec<SpanSource>,
|
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 {
|
impl ShErr {
|
||||||
pub fn new(kind: ShErrKind, span: Span) -> Self {
|
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 {
|
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 {
|
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
|
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))
|
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
}
|
}
|
||||||
pub fn blame(self, span: Span) -> Self {
|
pub fn blame(self, span: Span) -> Self {
|
||||||
let ShErr { kind, src_span: _, labels, sources, notes } = self;
|
let ShErr { kind, src_span: _, labels, sources, notes, io_guards } = self;
|
||||||
Self { kind, src_span: Some(span), labels, sources, notes }
|
Self { kind, src_span: Some(span), labels, sources, notes, io_guards }
|
||||||
}
|
}
|
||||||
pub fn try_blame(self, span: Span) -> Self {
|
pub fn try_blame(self, span: Span) -> Self {
|
||||||
match 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
|
_ => self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,23 +212,23 @@ impl ShErr {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> 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);
|
sources.push(source);
|
||||||
labels.push(label);
|
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 {
|
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 {
|
for (src, label) in ctx {
|
||||||
sources.push(src);
|
sources.push(src);
|
||||||
labels.push(label);
|
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 {
|
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());
|
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>> {
|
pub fn build_report(&self) -> Option<Report<'_, Span>> {
|
||||||
let span = self.src_span.as_ref()?;
|
let span = self.src_span.as_ref()?;
|
||||||
@@ -313,8 +328,7 @@ pub enum ShErrKind {
|
|||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno(Errno),
|
Errno(Errno),
|
||||||
FileNotFound,
|
NotFound,
|
||||||
CmdNotFound,
|
|
||||||
ReadlineErr,
|
ReadlineErr,
|
||||||
|
|
||||||
// Not really errors, more like internal signals
|
// Not really errors, more like internal signals
|
||||||
@@ -339,8 +353,7 @@ impl Display for ShErrKind {
|
|||||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
Self::BadPermission => "Bad Permissions",
|
Self::BadPermission => "Bad Permissions",
|
||||||
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
||||||
Self::FileNotFound => "File not found",
|
Self::NotFound => "Not Found",
|
||||||
Self::CmdNotFound => "Command not found",
|
|
||||||
Self::CleanExit(_) => "",
|
Self::CleanExit(_) => "",
|
||||||
Self::FuncReturn(_) => "Syntax Error",
|
Self::FuncReturn(_) => "Syntax Error",
|
||||||
Self::LoopContinue(_) => "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 error;
|
||||||
pub mod flog;
|
pub mod flog;
|
||||||
|
pub mod guards;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
pub mod term;
|
pub mod term;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
@@ -1,52 +1,7 @@
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use termios::{LocalFlags, Termios};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
||||||
open("/dev/tty", OFlag::O_RDWR, Mode::empty()).expect("Failed to open /dev/tty")
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@@ -28,7 +28,7 @@ use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
|
|||||||
use nix::unistd::read;
|
use nix::unistd::read;
|
||||||
|
|
||||||
use crate::builtin::trap::TrapTarget;
|
use crate::builtin::trap::TrapTarget;
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{self, ShErr, ShErrKind, ShResult};
|
||||||
use crate::libsh::sys::TTY_FILENO;
|
use crate::libsh::sys::TTY_FILENO;
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -112,7 +112,7 @@ fn main() -> ExitCode {
|
|||||||
if let Err(e) = if let Some(path) = args.script {
|
if let Err(e) = if let Some(path) = args.script {
|
||||||
run_script(path, args.script_args)
|
run_script(path, args.script_args)
|
||||||
} else if let Some(cmd) = args.command {
|
} else if let Some(cmd) = args.command {
|
||||||
exec_input(cmd, None, false)
|
exec_input(cmd, None, false, None)
|
||||||
} else {
|
} else {
|
||||||
shed_interactive()
|
shed_interactive()
|
||||||
} {
|
} {
|
||||||
@@ -120,8 +120,7 @@ fn main() -> ExitCode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
|
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
|
||||||
&& let Err(e) = exec_input(trap, None, false)
|
&& let Err(e) = exec_input(trap, None, false, Some("trap".into())) {
|
||||||
{
|
|
||||||
eprintln!("shed: error running EXIT trap: {e}");
|
eprintln!("shed: error running EXIT trap: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +130,7 @@ fn main() -> ExitCode {
|
|||||||
|
|
||||||
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
let path_raw = path.to_string_lossy().to_string();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
eprintln!("shed: Failed to open input file: {}", path.display());
|
eprintln!("shed: Failed to open input file: {}", path.display());
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
@@ -156,7 +156,7 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
|||||||
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
exec_input(input, None, false)
|
exec_input(input, None, false, Some(path_raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shed_interactive() -> ShResult<()> {
|
fn shed_interactive() -> ShResult<()> {
|
||||||
@@ -186,6 +186,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
m.try_rehash_commands();
|
m.try_rehash_commands();
|
||||||
m.try_rehash_cwd_listing();
|
m.try_rehash_cwd_listing();
|
||||||
});
|
});
|
||||||
|
error::clear_color();
|
||||||
|
|
||||||
// Handle any pending signals
|
// Handle any pending signals
|
||||||
while signals_pending() {
|
while signals_pending() {
|
||||||
@@ -265,7 +266,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
Ok(ReadlineEvent::Line(input)) => {
|
Ok(ReadlineEvent::Line(input)) => {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
write_meta(|m| m.start_timer());
|
write_meta(|m| m.start_timer());
|
||||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true)) {
|
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true, Some("<stdin>".into()))) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ use ariadne::Fmt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||||
},
|
},
|
||||||
expand::{expand_aliases, glob_to_regex},
|
expand::{expand_aliases, glob_to_regex},
|
||||||
jobs::{ChildProc, JobStack, dispatch_job},
|
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
|
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
|
||||||
|
guards::{scope_guard, var_ctx_guard},
|
||||||
utils::RedirVecUtils,
|
utils::RedirVecUtils,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -66,48 +67,6 @@ pub fn is_in_path(name: &str) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScopeGuard;
|
|
||||||
|
|
||||||
impl ScopeGuard {
|
|
||||||
pub fn exclusive_scope(args: Option<Vec<(String, Span)>>) -> Self {
|
|
||||||
let argv = args.map(|a| a.into_iter().map(|(s, _)| s).collect::<Vec<_>>());
|
|
||||||
write_vars(|v| v.descend(argv));
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
pub fn shared_scope() -> Self {
|
|
||||||
// used in environments that inherit from the parent, like subshells
|
|
||||||
write_vars(|v| v.descend(None));
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ScopeGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
write_vars(|v| v.ascend());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to throw away variables that exist in temporary contexts
|
|
||||||
/// such as 'VAR=value <command> <args>'
|
|
||||||
/// or for-loop variables
|
|
||||||
pub struct VarCtxGuard {
|
|
||||||
vars: HashSet<String>,
|
|
||||||
}
|
|
||||||
impl VarCtxGuard {
|
|
||||||
pub fn new(vars: HashSet<String>) -> Self {
|
|
||||||
Self { vars }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for VarCtxGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
write_vars(|v| {
|
|
||||||
for var in &self.vars {
|
|
||||||
v.unset_var(var).ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AssignBehavior {
|
pub enum AssignBehavior {
|
||||||
Export,
|
Export,
|
||||||
Set,
|
Set,
|
||||||
@@ -151,7 +110,7 @@ impl ExecArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -> ShResult<()> {
|
pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, source_name: Option<String>) -> ShResult<()> {
|
||||||
let log_tab = read_logic(|l| l.clone());
|
let log_tab = read_logic(|l| l.clone());
|
||||||
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
||||||
let lex_flags = if interactive {
|
let lex_flags = if interactive {
|
||||||
@@ -159,7 +118,8 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
} else {
|
} else {
|
||||||
super::lex::LexFlags::empty()
|
super::lex::LexFlags::empty()
|
||||||
};
|
};
|
||||||
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags);
|
let source_name = source_name.unwrap_or("<stdin>".into());
|
||||||
|
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags).with_name(source_name.clone());
|
||||||
if let Err(errors) = parser.parse_src() {
|
if let Err(errors) = parser.parse_src() {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
error.print_error();
|
error.print_error();
|
||||||
@@ -169,7 +129,7 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
|
|
||||||
let nodes = parser.extract_nodes();
|
let nodes = parser.extract_nodes();
|
||||||
|
|
||||||
let mut dispatcher = Dispatcher::new(nodes, interactive);
|
let mut dispatcher = Dispatcher::new(nodes, interactive, source_name.clone());
|
||||||
if let Some(mut stack) = io_stack {
|
if let Some(mut stack) = io_stack {
|
||||||
dispatcher.io_stack.extend(stack.drain(..));
|
dispatcher.io_stack.extend(stack.drain(..));
|
||||||
}
|
}
|
||||||
@@ -179,7 +139,7 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
&& let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Error))
|
&& let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Error))
|
||||||
{
|
{
|
||||||
let saved_status = state::get_status();
|
let saved_status = state::get_status();
|
||||||
exec_input(trap, None, false)?;
|
exec_input(trap, None, false, Some(source_name))?;
|
||||||
state::set_status(saved_status);
|
state::set_status(saved_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,16 +149,18 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
nodes: VecDeque<Node>,
|
nodes: VecDeque<Node>,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
|
source_name: String,
|
||||||
pub io_stack: IoStack,
|
pub io_stack: IoStack,
|
||||||
pub job_stack: JobStack,
|
pub job_stack: JobStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
pub fn new(nodes: Vec<Node>, interactive: bool) -> Self {
|
pub fn new(nodes: Vec<Node>, interactive: bool, source_name: String) -> Self {
|
||||||
let nodes = VecDeque::from(nodes);
|
let nodes = VecDeque::from(nodes);
|
||||||
Self {
|
Self {
|
||||||
nodes,
|
nodes,
|
||||||
interactive,
|
interactive,
|
||||||
|
source_name,
|
||||||
io_stack: IoStack::new(),
|
io_stack: IoStack::new(),
|
||||||
job_stack: JobStack::new(),
|
job_stack: JobStack::new(),
|
||||||
}
|
}
|
||||||
@@ -244,7 +206,7 @@ impl Dispatcher {
|
|||||||
let stack = IoStack {
|
let stack = IoStack {
|
||||||
stack: self.io_stack.clone(),
|
stack: self.io_stack.clone(),
|
||||||
};
|
};
|
||||||
exec_input(format!("cd {dir}"), Some(stack), self.interactive)
|
exec_input(format!("cd {dir}"), Some(stack), self.interactive, Some(self.source_name.clone()))
|
||||||
} else {
|
} else {
|
||||||
self.exec_cmd(node)
|
self.exec_cmd(node)
|
||||||
}
|
}
|
||||||
@@ -310,7 +272,7 @@ impl Dispatcher {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let func = ShFunc::new(func_parser);
|
let func = ShFunc::new(func_parser,blame);
|
||||||
write_logic(|l| l.insert_func(name, func)); // Store the AST
|
write_logic(|l| l.insert_func(name, func)); // Store the AST
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -319,6 +281,7 @@ impl Dispatcher {
|
|||||||
let NdRule::Command { assignments, argv } = subsh.class else {
|
let NdRule::Command { assignments, argv } = subsh.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
let name = self.source_name.clone();
|
||||||
|
|
||||||
self.run_fork("anonymous_subshell", |s| {
|
self.run_fork("anonymous_subshell", |s| {
|
||||||
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
||||||
@@ -337,7 +300,7 @@ impl Dispatcher {
|
|||||||
let subsh = argv.remove(0);
|
let subsh = argv.remove(0);
|
||||||
let subsh_body = subsh.0.to_string();
|
let subsh_body = subsh.0.to_string();
|
||||||
|
|
||||||
if let Err(e) = exec_input(subsh_body, None, s.interactive) {
|
if let Err(e) = exec_input(subsh_body, None, s.interactive, Some(name)) {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -371,7 +334,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
||||||
let func_name = argv.remove(0).to_string();
|
let func_name = argv.remove(0).to_string();
|
||||||
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
let _var_guard = var_ctx_guard(env_vars.into_iter().collect());
|
||||||
|
|
||||||
self.io_stack.append_to_frame(func.redirs);
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
|
|
||||||
@@ -379,7 +342,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
let argv = prepare_argv(argv).try_blame(blame.clone())?;
|
let argv = prepare_argv(argv).try_blame(blame.clone())?;
|
||||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
let _guard = scope_guard(Some(argv));
|
||||||
func_body.body_mut().propagate_context(func_ctx);
|
func_body.body_mut().propagate_context(func_ctx);
|
||||||
func_body.body_mut().flags = func.flags;
|
func_body.body_mut().flags = func.flags;
|
||||||
|
|
||||||
@@ -412,7 +375,7 @@ impl Dispatcher {
|
|||||||
let fork_builtins = brc_grp.flags.contains(NdFlags::FORK_BUILTINS);
|
let fork_builtins = brc_grp.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(brc_grp.redirs);
|
self.io_stack.append_to_frame(brc_grp.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let guard = self.io_stack.pop_frame().redirect()?;
|
||||||
let brc_grp_logic = |s: &mut Self| -> ShResult<()> {
|
let brc_grp_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
for node in body {
|
for node in body {
|
||||||
let blame = node.get_span();
|
let blame = node.get_span();
|
||||||
@@ -430,7 +393,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
brc_grp_logic(self)
|
brc_grp_logic(self).map_err(|e| e.with_redirs(guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_case(&mut self, case_stmt: Node) -> ShResult<()> {
|
fn exec_case(&mut self, case_stmt: Node) -> ShResult<()> {
|
||||||
@@ -446,7 +409,7 @@ impl Dispatcher {
|
|||||||
let fork_builtins = case_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
let fork_builtins = case_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(case_stmt.redirs);
|
self.io_stack.append_to_frame(case_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
let case_logic = |s: &mut Self| -> ShResult<()> {
|
let case_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let exp_pattern = pattern.clone().expand()?;
|
let exp_pattern = pattern.clone().expand()?;
|
||||||
@@ -484,7 +447,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
case_logic(self).try_blame(blame)
|
case_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
|
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
|
||||||
@@ -502,7 +465,7 @@ impl Dispatcher {
|
|||||||
let fork_builtins = loop_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
let fork_builtins = loop_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(loop_stmt.redirs);
|
self.io_stack.append_to_frame(loop_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
let loop_logic = |s: &mut Self| -> ShResult<()> {
|
let loop_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let CondNode { cond, body } = cond_node;
|
let CondNode { cond, body } = cond_node;
|
||||||
@@ -547,7 +510,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
loop_logic(self).try_blame(blame)
|
loop_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
||||||
@@ -571,14 +534,14 @@ impl Dispatcher {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.io_stack.append_to_frame(for_stmt.redirs);
|
self.io_stack.append_to_frame(for_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
let for_logic = |s: &mut Self| -> ShResult<()> {
|
let for_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
// Expand all array variables
|
// Expand all array variables
|
||||||
let arr: Vec<String> = to_expanded_strings(arr)?;
|
let arr: Vec<String> = to_expanded_strings(arr)?;
|
||||||
let vars: Vec<String> = to_expanded_strings(vars)?;
|
let vars: Vec<String> = to_expanded_strings(vars)?;
|
||||||
|
|
||||||
let mut for_guard = VarCtxGuard::new(vars.iter().map(|v| v.to_string()).collect());
|
let mut for_guard = var_ctx_guard(vars.iter().map(|v| v.to_string()).collect());
|
||||||
|
|
||||||
'outer: for chunk in arr.chunks(vars.len()) {
|
'outer: for chunk in arr.chunks(vars.len()) {
|
||||||
let empty = String::new();
|
let empty = String::new();
|
||||||
@@ -594,7 +557,7 @@ impl Dispatcher {
|
|||||||
VarFlags::NONE,
|
VarFlags::NONE,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
for_guard.vars.insert(var.to_string());
|
for_guard.insert(var.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in body.clone() {
|
for node in body.clone() {
|
||||||
@@ -625,7 +588,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
for_logic(self).try_blame(blame)
|
for_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
||||||
@@ -640,7 +603,7 @@ impl Dispatcher {
|
|||||||
let fork_builtins = if_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
let fork_builtins = if_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(if_stmt.redirs);
|
self.io_stack.append_to_frame(if_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
let if_logic = |s: &mut Self| -> ShResult<()> {
|
let if_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
@@ -682,7 +645,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if_logic(self).try_blame(blame)
|
if_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
|
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
|
||||||
@@ -696,6 +659,9 @@ impl Dispatcher {
|
|||||||
// Zip the commands and their respective pipes into an iterator
|
// Zip the commands and their respective pipes into an iterator
|
||||||
let pipes_and_cmds = get_pipe_stack(cmds.len()).into_iter().zip(cmds);
|
let pipes_and_cmds = get_pipe_stack(cmds.len()).into_iter().zip(cmds);
|
||||||
|
|
||||||
|
let is_bg = pipeline.flags.contains(NdFlags::BACKGROUND);
|
||||||
|
let mut tty_attached = false;
|
||||||
|
|
||||||
for ((rpipe, wpipe), mut cmd) in pipes_and_cmds {
|
for ((rpipe, wpipe), mut cmd) in pipes_and_cmds {
|
||||||
if let Some(pipe) = rpipe {
|
if let Some(pipe) = rpipe {
|
||||||
self.io_stack.push_to_frame(pipe);
|
self.io_stack.push_to_frame(pipe);
|
||||||
@@ -716,9 +682,18 @@ impl Dispatcher {
|
|||||||
cmd.flags |= NdFlags::FORK_BUILTINS;
|
cmd.flags |= NdFlags::FORK_BUILTINS;
|
||||||
}
|
}
|
||||||
self.dispatch_node(cmd)?;
|
self.dispatch_node(cmd)?;
|
||||||
|
|
||||||
|
// Give the pipeline terminal control as soon as the first child
|
||||||
|
// establishes the PGID, so later children (e.g. nvim) don't get
|
||||||
|
// SIGTTOU when they try to modify terminal attributes.
|
||||||
|
if !tty_attached && !is_bg {
|
||||||
|
if let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid() {
|
||||||
|
attach_tty(pgid).ok();
|
||||||
|
tty_attached = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let job = self.job_stack.finalize_job().unwrap();
|
let job = self.job_stack.finalize_job().unwrap();
|
||||||
let is_bg = pipeline.flags.contains(NdFlags::BACKGROUND);
|
|
||||||
dispatch_job(job, is_bg)?;
|
dispatch_job(job, is_bg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -757,10 +732,9 @@ impl Dispatcher {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let env_vars = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
||||||
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
let _var_guard = var_ctx_guard(env_vars.into_iter().collect());
|
||||||
|
|
||||||
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
// Handle builtin/command recursion before redirect/job setup
|
||||||
let io_stack_mut = &mut self.io_stack;
|
|
||||||
if cmd_raw.as_str() == "builtin" {
|
if cmd_raw.as_str() == "builtin" {
|
||||||
*argv = argv
|
*argv = argv
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@@ -779,43 +753,71 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
return self.exec_cmd(cmd);
|
return self.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up redirections here so we can attach the guard to propagated errors.
|
||||||
|
self.io_stack.append_to_frame(mem::take(&mut cmd.redirs));
|
||||||
|
let redir_guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
|
// Register ChildProc in current job
|
||||||
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
|
pgid
|
||||||
|
} else {
|
||||||
|
job.set_pgid(Pid::this());
|
||||||
|
Pid::this()
|
||||||
|
};
|
||||||
|
let child = ChildProc::new(Pid::this(), Some(&cmd_raw), Some(child_pgid))?;
|
||||||
|
job.push_child(child);
|
||||||
|
|
||||||
|
// Handle exec specially — persist redirections before dispatch
|
||||||
|
if cmd_raw.as_str() == "exec" {
|
||||||
|
redir_guard.persist();
|
||||||
|
let result = exec::exec_builtin(cmd);
|
||||||
|
return if let Err(e) = result {
|
||||||
|
Err(e.with_context(context))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let result = match cmd_raw.as_str() {
|
let result = match cmd_raw.as_str() {
|
||||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
"echo" => echo(cmd),
|
||||||
"cd" => cd(cmd, curr_job_mut),
|
"cd" => cd(cmd),
|
||||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
"export" => export(cmd),
|
||||||
"local" => local(cmd, io_stack_mut, curr_job_mut),
|
"local" => local(cmd),
|
||||||
"pwd" => pwd(cmd, io_stack_mut, curr_job_mut),
|
"pwd" => pwd(cmd),
|
||||||
"source" => source(cmd, curr_job_mut),
|
"source" => source(cmd),
|
||||||
"shift" => shift(cmd, curr_job_mut),
|
"shift" => shift(cmd),
|
||||||
"fg" => continue_job(cmd, curr_job_mut, JobBehavior::Foregound),
|
"fg" => continue_job(cmd, JobBehavior::Foregound),
|
||||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
"bg" => continue_job(cmd, JobBehavior::Background),
|
||||||
"disown" => disown(cmd, io_stack_mut, curr_job_mut),
|
"disown" => disown(cmd),
|
||||||
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
"jobs" => jobs(cmd),
|
||||||
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
"alias" => alias(cmd),
|
||||||
"unalias" => unalias(cmd, io_stack_mut, curr_job_mut),
|
"unalias" => unalias(cmd),
|
||||||
"return" => flowctl(cmd, ShErrKind::FuncReturn(0)),
|
"return" => flowctl(cmd, ShErrKind::FuncReturn(0)),
|
||||||
"break" => flowctl(cmd, ShErrKind::LoopBreak(0)),
|
"break" => flowctl(cmd, ShErrKind::LoopBreak(0)),
|
||||||
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
||||||
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
||||||
"zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut),
|
"zoltraak" => zoltraak(cmd),
|
||||||
"shopt" => shopt(cmd, io_stack_mut, curr_job_mut),
|
"shopt" => shopt(cmd),
|
||||||
"read" => read_builtin(cmd, io_stack_mut, curr_job_mut),
|
"read" => read_builtin(cmd),
|
||||||
"trap" => trap(cmd, io_stack_mut, curr_job_mut),
|
"trap" => trap(cmd),
|
||||||
"pushd" => pushd(cmd, io_stack_mut, curr_job_mut),
|
"pushd" => pushd(cmd),
|
||||||
"popd" => popd(cmd, io_stack_mut, curr_job_mut),
|
"popd" => popd(cmd),
|
||||||
"dirs" => dirs(cmd, io_stack_mut, curr_job_mut),
|
"dirs" => dirs(cmd),
|
||||||
"exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut),
|
"eval" => eval::eval(cmd),
|
||||||
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
|
"readonly" => readonly(cmd),
|
||||||
"readonly" => readonly(cmd, io_stack_mut, curr_job_mut),
|
"unset" => unset(cmd),
|
||||||
"unset" => unset(cmd, io_stack_mut, curr_job_mut),
|
"complete" => complete_builtin(cmd),
|
||||||
"complete" => complete_builtin(cmd, io_stack_mut, curr_job_mut),
|
"compgen" => compgen_builtin(cmd),
|
||||||
"compgen" => compgen_builtin(cmd, io_stack_mut, curr_job_mut),
|
"map" => map::map(cmd),
|
||||||
"map" => map::map(cmd, io_stack_mut, curr_job_mut),
|
"pop" => arr_pop(cmd),
|
||||||
"pop" => arr_pop(cmd, io_stack_mut, curr_job_mut),
|
"fpop" => arr_fpop(cmd),
|
||||||
"fpop" => arr_fpop(cmd, io_stack_mut, curr_job_mut),
|
"push" => arr_push(cmd),
|
||||||
"push" => arr_push(cmd, io_stack_mut, curr_job_mut),
|
"fpush" => arr_fpush(cmd),
|
||||||
"fpush" => arr_fpush(cmd, io_stack_mut, curr_job_mut),
|
"rotate" => arr_rotate(cmd),
|
||||||
"rotate" => arr_rotate(cmd, io_stack_mut, curr_job_mut),
|
"wait" => jobctl::wait(cmd),
|
||||||
|
"type" => intro::type_builtin(cmd),
|
||||||
"true" | ":" => {
|
"true" | ":" => {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -828,7 +830,7 @@ impl Dispatcher {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
Err(e.with_context(context))
|
Err(e.with_context(context).with_redirs(redir_guard))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -861,8 +863,21 @@ impl Dispatcher {
|
|||||||
let exec_args = ExecArgs::new(argv).blame(blame)?;
|
let exec_args = ExecArgs::new(argv).blame(blame)?;
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
let job = self.job_stack.curr_job_mut().unwrap();
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
|
let existing_pgid = job.pgid();
|
||||||
|
|
||||||
|
let child_logic = |pgid: Option<Pid>| -> ! {
|
||||||
|
// Put ourselves in the correct process group before exec.
|
||||||
|
// For the first child in a pipeline pgid is None, so we
|
||||||
|
// become our own group leader (setpgid(0,0)). For later
|
||||||
|
// children we join the leader's group.
|
||||||
|
let _ = setpgid(Pid::from_raw(0), pgid.unwrap_or(Pid::from_raw(0)));
|
||||||
|
|
||||||
|
// Reset signal dispositions before exec. SIG_IGN is preserved
|
||||||
|
// across execvpe, so the shell's ignored SIGTTIN/SIGTTOU would
|
||||||
|
// leak into child processes and break programs like nvim that
|
||||||
|
// need default terminal-stop behavior.
|
||||||
|
crate::signal::reset_signals();
|
||||||
|
|
||||||
let child_logic = || -> ! {
|
|
||||||
let cmd = &exec_args.cmd.0;
|
let cmd = &exec_args.cmd.0;
|
||||||
let span = exec_args.cmd.1;
|
let span = exec_args.cmd.1;
|
||||||
|
|
||||||
@@ -872,7 +887,7 @@ impl Dispatcher {
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => {
|
Errno::ENOENT => {
|
||||||
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
ShErr::new(ShErrKind::NotFound, span.clone())
|
||||||
.labeled(span, format!("{cmd_str}: command not found"))
|
.labeled(span, format!("{cmd_str}: command not found"))
|
||||||
.with_context(context)
|
.with_context(context)
|
||||||
.print_error();
|
.print_error();
|
||||||
@@ -887,11 +902,11 @@ impl Dispatcher {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if no_fork {
|
if no_fork {
|
||||||
child_logic();
|
child_logic(existing_pgid);
|
||||||
}
|
}
|
||||||
|
|
||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => child_logic(),
|
ForkResult::Child => child_logic(existing_pgid),
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
// Close proc sub pipe fds - the child has inherited them
|
// Close proc sub pipe fds - the child has inherited them
|
||||||
// and will access them via /proc/self/fd/N. Keeping them
|
// and will access them via /proc/self/fd/N. Keeping them
|
||||||
@@ -900,7 +915,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
let cmd_name = exec_args.cmd.0.to_str().unwrap();
|
let cmd_name = exec_args.cmd.0.to_str().unwrap();
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = existing_pgid {
|
||||||
pgid
|
pgid
|
||||||
} else {
|
} else {
|
||||||
job.set_pgid(child);
|
job.set_pgid(child);
|
||||||
@@ -918,15 +933,18 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn run_fork(&mut self, name: &str, f: impl FnOnce(&mut Self)) -> ShResult<()> {
|
fn run_fork(&mut self, name: &str, f: impl FnOnce(&mut Self)) -> ShResult<()> {
|
||||||
|
let existing_pgid = self.job_stack.curr_job_mut().unwrap().pgid();
|
||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
|
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
||||||
|
crate::signal::reset_signals();
|
||||||
f(self);
|
f(self);
|
||||||
exit(state::get_status())
|
exit(state::get_status())
|
||||||
}
|
}
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
write_jobs(|j| j.drain_registered_fds());
|
write_jobs(|j| j.drain_registered_fds());
|
||||||
let job = self.job_stack.curr_job_mut().unwrap();
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = existing_pgid {
|
||||||
pgid
|
pgid
|
||||||
} else {
|
} else {
|
||||||
job.set_pgid(child);
|
job.set_pgid(child);
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ impl Span {
|
|||||||
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
|
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
|
||||||
let source = SpanSource { name: "<stdin>".into(), content: source };
|
let source = SpanSource { name: "<stdin>".into(), content: source };
|
||||||
Span { range, source }
|
Span { range, source }
|
||||||
|
}
|
||||||
|
pub fn from_span_source(range: Range<usize>, source: SpanSource) -> Self {
|
||||||
|
Span { range, source }
|
||||||
}
|
}
|
||||||
pub fn rename(&mut self, name: String) {
|
pub fn rename(&mut self, name: String) {
|
||||||
self.source.name = name;
|
self.source.name = name;
|
||||||
@@ -108,6 +111,12 @@ impl Span {
|
|||||||
self.source.name = name;
|
self.source.name = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn line_and_col(&self) -> (usize,usize) {
|
||||||
|
let content = self.source.content();
|
||||||
|
let source = ariadne::Source::from(content.as_str());
|
||||||
|
let (_, line, col) = source.get_byte_line(self.range.start).unwrap();
|
||||||
|
(line, col)
|
||||||
|
}
|
||||||
/// Slice the source string at the wrapped range
|
/// Slice the source string at the wrapped range
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.source.content[self.range().start..self.range().end]
|
&self.source.content[self.range().start..self.range().end]
|
||||||
@@ -234,7 +243,9 @@ bitflags! {
|
|||||||
pub struct LexStream {
|
pub struct LexStream {
|
||||||
source: Arc<String>,
|
source: Arc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
|
pub name: String,
|
||||||
quote_state: QuoteState,
|
quote_state: QuoteState,
|
||||||
|
brc_grp_depth: usize,
|
||||||
brc_grp_start: Option<usize>,
|
brc_grp_start: Option<usize>,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
}
|
}
|
||||||
@@ -243,23 +254,21 @@ bitflags! {
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct LexFlags: u32 {
|
pub struct LexFlags: u32 {
|
||||||
/// The lexer is operating in interactive mode
|
/// The lexer is operating in interactive mode
|
||||||
const INTERACTIVE = 0b000000001;
|
const INTERACTIVE = 0b0000000001;
|
||||||
/// Allow unfinished input
|
/// Allow unfinished input
|
||||||
const LEX_UNFINISHED = 0b000000010;
|
const LEX_UNFINISHED = 0b0000000010;
|
||||||
/// The next string-type token is a command name
|
/// The next string-type token is a command name
|
||||||
const NEXT_IS_CMD = 0b000000100;
|
const NEXT_IS_CMD = 0b0000000100;
|
||||||
/// We are in a quotation, so quoting rules apply
|
/// We are in a quotation, so quoting rules apply
|
||||||
const IN_QUOTE = 0b000001000;
|
const IN_QUOTE = 0b0000001000;
|
||||||
/// Only lex strings; used in expansions
|
/// Only lex strings; used in expansions
|
||||||
const RAW = 0b000010000;
|
const RAW = 0b0000010000;
|
||||||
/// The lexer has not produced any tokens yet
|
/// The lexer has not produced any tokens yet
|
||||||
const FRESH = 0b000010000;
|
const FRESH = 0b0000100000;
|
||||||
/// The lexer has no more tokens to produce
|
/// The lexer has no more tokens to produce
|
||||||
const STALE = 0b000100000;
|
const STALE = 0b0001000000;
|
||||||
/// The lexer's cursor is in a brace group
|
const EXPECTING_IN = 0b0010000000;
|
||||||
const IN_BRC_GRP = 0b001000000;
|
const IN_CASE = 0b0100000000;
|
||||||
const EXPECTING_IN = 0b010000000;
|
|
||||||
const IN_CASE = 0b100000000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,8 +278,10 @@ impl LexStream {
|
|||||||
Self {
|
Self {
|
||||||
flags,
|
flags,
|
||||||
source,
|
source,
|
||||||
|
name: "<stdin>".into(),
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
quote_state: QuoteState::default(),
|
quote_state: QuoteState::default(),
|
||||||
|
brc_grp_depth: 0,
|
||||||
brc_grp_start: None,
|
brc_grp_start: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,18 +307,25 @@ impl LexStream {
|
|||||||
};
|
};
|
||||||
self.source.get(start..end)
|
self.source.get(start..end)
|
||||||
}
|
}
|
||||||
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
|
self.name = name;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn slice_from_cursor(&self) -> Option<&str> {
|
pub fn slice_from_cursor(&self) -> Option<&str> {
|
||||||
self.slice(self.cursor..)
|
self.slice(self.cursor..)
|
||||||
}
|
}
|
||||||
pub fn in_brc_grp(&self) -> bool {
|
pub fn in_brc_grp(&self) -> bool {
|
||||||
self.flags.contains(LexFlags::IN_BRC_GRP)
|
self.brc_grp_depth > 0
|
||||||
}
|
}
|
||||||
pub fn set_in_brc_grp(&mut self, is: bool) {
|
pub fn enter_brc_grp(&mut self) {
|
||||||
if is {
|
if self.brc_grp_depth == 0 {
|
||||||
self.flags |= LexFlags::IN_BRC_GRP;
|
|
||||||
self.brc_grp_start = Some(self.cursor);
|
self.brc_grp_start = Some(self.cursor);
|
||||||
} else {
|
}
|
||||||
self.flags &= !LexFlags::IN_BRC_GRP;
|
self.brc_grp_depth += 1;
|
||||||
|
}
|
||||||
|
pub fn leave_brc_grp(&mut self) {
|
||||||
|
self.brc_grp_depth -= 1;
|
||||||
|
if self.brc_grp_depth == 0 {
|
||||||
self.brc_grp_start = None;
|
self.brc_grp_start = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,7 +645,7 @@ impl LexStream {
|
|||||||
pos += 1;
|
pos += 1;
|
||||||
let mut tk = self.get_token(self.cursor..pos, TkRule::BraceGrpStart);
|
let mut tk = self.get_token(self.cursor..pos, TkRule::BraceGrpStart);
|
||||||
tk.flags |= TkFlags::IS_CMD;
|
tk.flags |= TkFlags::IS_CMD;
|
||||||
self.set_in_brc_grp(true);
|
self.enter_brc_grp();
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
|
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
@@ -636,7 +654,7 @@ impl LexStream {
|
|||||||
'}' if pos == self.cursor && self.in_brc_grp() => {
|
'}' if pos == self.cursor && self.in_brc_grp() => {
|
||||||
pos += 1;
|
pos += 1;
|
||||||
let tk = self.get_token(self.cursor..pos, TkRule::BraceGrpEnd);
|
let tk = self.get_token(self.cursor..pos, TkRule::BraceGrpEnd);
|
||||||
self.set_in_brc_grp(false);
|
self.leave_brc_grp();
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Ok(tk);
|
return Ok(tk);
|
||||||
@@ -731,7 +749,8 @@ impl LexStream {
|
|||||||
Ok(new_tk)
|
Ok(new_tk)
|
||||||
}
|
}
|
||||||
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
||||||
let span = Span::new(range, self.source.clone());
|
let mut span = Span::new(range, self.source.clone());
|
||||||
|
span.rename(self.name.clone());
|
||||||
Tk::new(class, span)
|
Tk::new(class, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ macro_rules! try_match {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ParsedSrc {
|
pub struct ParsedSrc {
|
||||||
pub src: Arc<String>,
|
pub src: Arc<String>,
|
||||||
|
pub name: String,
|
||||||
pub ast: Ast,
|
pub ast: Ast,
|
||||||
pub lex_flags: LexFlags,
|
pub lex_flags: LexFlags,
|
||||||
pub context: LabelCtx,
|
pub context: LabelCtx,
|
||||||
@@ -53,11 +54,16 @@ impl ParsedSrc {
|
|||||||
pub fn new(src: Arc<String>) -> Self {
|
pub fn new(src: Arc<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
src,
|
src,
|
||||||
|
name: "<stdin>".into(),
|
||||||
ast: Ast::new(vec![]),
|
ast: Ast::new(vec![]),
|
||||||
lex_flags: LexFlags::empty(),
|
lex_flags: LexFlags::empty(),
|
||||||
context: VecDeque::new(),
|
context: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
|
self.name = name;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
||||||
self.lex_flags = flags;
|
self.lex_flags = flags;
|
||||||
self
|
self
|
||||||
@@ -68,7 +74,7 @@ impl ParsedSrc {
|
|||||||
}
|
}
|
||||||
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
|
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
for lex_result in LexStream::new(self.src.clone(), self.lex_flags) {
|
for lex_result in LexStream::new(self.src.clone(), self.lex_flags).with_name(self.name.clone()) {
|
||||||
match lex_result {
|
match lex_result {
|
||||||
Ok(token) => tokens.push(token),
|
Ok(token) => tokens.push(token),
|
||||||
Err(error) => return Err(vec![error]),
|
Err(error) => return Err(vec![error]),
|
||||||
@@ -244,9 +250,9 @@ impl Node {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
Span::new(
|
Span::from_span_source(
|
||||||
first_tk.span.range().start..last_tk.span.range().end,
|
first_tk.span.range().start..last_tk.span.range().end,
|
||||||
first_tk.span.get_source(),
|
first_tk.span.span_source().clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,39 +154,19 @@ impl<R: Read> IoBuf<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RedirGuard(IoFrame);
|
pub use crate::libsh::guards::RedirGuard;
|
||||||
|
|
||||||
impl RedirGuard {
|
|
||||||
pub fn persist(mut self) {
|
|
||||||
if let Some(saved) = self.0.saved_io.take() {
|
|
||||||
close(saved.0).ok();
|
|
||||||
close(saved.1).ok();
|
|
||||||
close(saved.2).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// the guard is dropped here
|
|
||||||
// but since we took the saved fds
|
|
||||||
// the drop does not restore them
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RedirGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.0.restore().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and
|
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and
|
||||||
/// `stderr` respectively
|
/// `stderr` respectively
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IoGroup(RawFd, RawFd, RawFd);
|
pub struct IoGroup(pub(crate) RawFd, pub(crate) RawFd, pub(crate) RawFd);
|
||||||
|
|
||||||
/// A single stack frame used with the IoStack
|
/// A single stack frame used with the IoStack
|
||||||
/// Each stack frame represents the redirections of a single command
|
/// Each stack frame represents the redirections of a single command
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct IoFrame {
|
pub struct IoFrame {
|
||||||
pub redirs: Vec<Redir>,
|
pub redirs: Vec<Redir>,
|
||||||
saved_io: Option<IoGroup>,
|
pub(crate) saved_io: Option<IoGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e> IoFrame {
|
impl<'e> IoFrame {
|
||||||
@@ -241,7 +221,7 @@ impl<'e> IoFrame {
|
|||||||
let src_fd = io_mode.src_fd();
|
let src_fd = io_mode.src_fd();
|
||||||
dup2(src_fd, tgt_fd)?;
|
dup2(src_fd, tgt_fd)?;
|
||||||
}
|
}
|
||||||
Ok(RedirGuard(self))
|
Ok(RedirGuard::new(self))
|
||||||
}
|
}
|
||||||
pub fn restore(&mut self) -> ShResult<()> {
|
pub fn restore(&mut self) -> ShResult<()> {
|
||||||
if let Some(saved) = self.saved_io.take() {
|
if let Some(saved) = self.saved_io.take() {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ use crate::{
|
|||||||
builtin::complete::{CompFlags, CompOptFlags, CompOpts},
|
builtin::complete::{CompFlags, CompOptFlags, CompOpts},
|
||||||
libsh::{
|
libsh::{
|
||||||
error::ShResult,
|
error::ShResult,
|
||||||
|
guards::var_ctx_guard,
|
||||||
utils::TkVecUtils,
|
utils::TkVecUtils,
|
||||||
},
|
},
|
||||||
parse::{
|
parse::{
|
||||||
execute::{VarCtxGuard, exec_input},
|
execute::exec_input,
|
||||||
lex::{self, LexFlags, Tk, TkRule, ends_with_unescaped},
|
lex::{self, LexFlags, Tk, TkRule, ends_with_unescaped},
|
||||||
},
|
},
|
||||||
readline::{
|
readline::{
|
||||||
@@ -341,7 +342,7 @@ impl BashCompSpec {
|
|||||||
] {
|
] {
|
||||||
vars_to_unset.insert(var.to_string());
|
vars_to_unset.insert(var.to_string());
|
||||||
}
|
}
|
||||||
let _guard = VarCtxGuard::new(vars_to_unset);
|
let _guard = var_ctx_guard(vars_to_unset);
|
||||||
|
|
||||||
let CompContext {
|
let CompContext {
|
||||||
words,
|
words,
|
||||||
@@ -391,7 +392,7 @@ impl BashCompSpec {
|
|||||||
"{} {cmd_name} {cword_str} {pword_str}",
|
"{} {cmd_name} {cword_str} {pword_str}",
|
||||||
self.function.as_ref().unwrap()
|
self.function.as_ref().unwrap()
|
||||||
);
|
);
|
||||||
exec_input(input, None, false)?;
|
exec_input(input, None, false, Some("comp_function".into()))?;
|
||||||
|
|
||||||
Ok(read_vars(|v| v.get_arr_elems("COMPREPLY")).unwrap_or_default())
|
Ok(read_vars(|v| v.get_arr_elems("COMPREPLY")).unwrap_or_default())
|
||||||
}
|
}
|
||||||
@@ -532,7 +533,7 @@ impl Completer {
|
|||||||
(before_cursor, after_cursor)
|
(before_cursor, after_cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_completion_context(&self, line: &str, cursor_pos: usize) -> (Vec<Marker>, usize) {
|
pub fn get_subtoken_completion(&self, line: &str, cursor_pos: usize) -> (Vec<Marker>, usize) {
|
||||||
let annotated = annotate_input_recursive(line);
|
let annotated = annotate_input_recursive(line);
|
||||||
let mut ctx = vec![markers::NULL];
|
let mut ctx = vec![markers::NULL];
|
||||||
let mut last_priority = 0;
|
let mut last_priority = 0;
|
||||||
@@ -776,20 +777,19 @@ impl Completer {
|
|||||||
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
||||||
// inside a token). Run this before comp specs so variable completions take
|
// inside a token). Run this before comp specs so variable completions take
|
||||||
// priority over programmable completion.
|
// priority over programmable completion.
|
||||||
let (mut marker_ctx, token_start) = self.get_completion_context(&line, cursor_pos);
|
let (mut marker_ctx, token_start) = self.get_subtoken_completion(&line, cursor_pos);
|
||||||
|
|
||||||
if marker_ctx.last() == Some(&markers::VAR_SUB) {
|
if marker_ctx.last() == Some(&markers::VAR_SUB)
|
||||||
if let Some(cur) = ctx.words.get(ctx.cword) {
|
&& let Some(cur) = ctx.words.get(ctx.cword) {
|
||||||
self.token_span.0 = token_start;
|
self.token_span.0 = token_start;
|
||||||
let mut span = cur.span.clone();
|
let mut span = cur.span.clone();
|
||||||
span.set_range(token_start..self.token_span.1);
|
span.set_range(token_start..self.token_span.1);
|
||||||
let raw_tk = span.as_str();
|
let raw_tk = span.as_str();
|
||||||
let candidates = complete_vars(raw_tk);
|
let candidates = complete_vars(raw_tk);
|
||||||
if !candidates.is_empty() {
|
if !candidates.is_empty() {
|
||||||
return Ok(CompResult::from_candidates(candidates));
|
return Ok(CompResult::from_candidates(candidates));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Try programmable completion
|
// Try programmable completion
|
||||||
match self.try_comp_spec(&ctx)? {
|
match self.try_comp_spec(&ctx)? {
|
||||||
|
|||||||
@@ -785,6 +785,10 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
(start, end)
|
(start, end)
|
||||||
}
|
}
|
||||||
|
pub fn this_line_content(&mut self) -> Option<&str> {
|
||||||
|
let (start,end) = self.this_line_exclusive();
|
||||||
|
self.slice(start..end)
|
||||||
|
}
|
||||||
pub fn this_line(&mut self) -> (usize, usize) {
|
pub fn this_line(&mut self) -> (usize, usize) {
|
||||||
let line_no = self.cursor_line_number();
|
let line_no = self.cursor_line_number();
|
||||||
self.line_bounds(line_no)
|
self.line_bounds(line_no)
|
||||||
@@ -2801,6 +2805,25 @@ impl LineBuf {
|
|||||||
Verb::InsertChar(ch) => {
|
Verb::InsertChar(ch) => {
|
||||||
self.insert_at_cursor(ch);
|
self.insert_at_cursor(ch);
|
||||||
self.cursor.add(1);
|
self.cursor.add(1);
|
||||||
|
let before = self.auto_indent_level;
|
||||||
|
if read_shopts(|o| o.prompt.auto_indent)
|
||||||
|
&& let Some(line_content) = self.this_line_content() {
|
||||||
|
match line_content.trim() {
|
||||||
|
"esac" | "done" | "fi" | "}" => {
|
||||||
|
self.calc_indent_level();
|
||||||
|
if self.auto_indent_level < before {
|
||||||
|
let delta = before - self.auto_indent_level;
|
||||||
|
let line_start = self.start_of_line();
|
||||||
|
for _ in 0..delta {
|
||||||
|
if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") {
|
||||||
|
self.remove(line_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* nothing to see here */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Verb::Insert(string) => {
|
Verb::Insert(string) => {
|
||||||
self.push_str(&string);
|
self.push_str(&string);
|
||||||
|
|||||||
@@ -10,52 +10,24 @@ use nix::{
|
|||||||
errno::Errno,
|
errno::Errno,
|
||||||
libc::{self},
|
libc::{self},
|
||||||
poll::{self, PollFlags, PollTimeout},
|
poll::{self, PollFlags, PollTimeout},
|
||||||
sys::termios::{self, tcgetattr, tcsetattr},
|
|
||||||
unistd::isatty,
|
unistd::isatty,
|
||||||
};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
use vte::{Parser, Perform};
|
use vte::{Parser, Perform};
|
||||||
|
|
||||||
|
pub use crate::libsh::guards::{RawModeGuard, raw_mode};
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
error::{ShErr, ShErrKind, ShResult},
|
|
||||||
sys::TTY_FILENO,
|
|
||||||
},
|
|
||||||
readline::keys::{KeyCode, ModKeys},
|
readline::keys::{KeyCode, ModKeys},
|
||||||
state::read_shopts,
|
state::read_shopts,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
procio::borrow_fd,
|
|
||||||
state::{read_meta, write_meta},
|
state::{read_meta, write_meta},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::keys::KeyEvent;
|
use super::keys::KeyEvent;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
RawModeGuard {
|
|
||||||
orig,
|
|
||||||
fd: *TTY_FILENO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Row = u16;
|
pub type Row = u16;
|
||||||
pub type Col = u16;
|
pub type Col = u16;
|
||||||
|
|
||||||
@@ -325,65 +297,6 @@ impl Read for TermBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 raw = tcgetattr(borrow_fd(*TTY_FILENO)).expect("Failed to get terminal attributes");
|
|
||||||
let mut cooked = raw.clone();
|
|
||||||
cooked.local_flags |= termios::LocalFlags::ICANON | termios::LocalFlags::ECHO;
|
|
||||||
cooked.input_flags |= termios::InputFlags::ICRNL;
|
|
||||||
tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, &cooked)
|
|
||||||
.expect("Failed to set cooked mode");
|
|
||||||
let res = f();
|
|
||||||
tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, &raw)
|
|
||||||
.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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PollReader - non-blocking key reader using vte parser
|
// PollReader - non-blocking key reader using vte parser
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn check_signals() -> ShResult<()> {
|
|||||||
let got_signal = |sig: Signal| -> bool { pending & (1 << sig as u64) != 0 };
|
let got_signal = |sig: Signal| -> bool { pending & (1 << sig as u64) != 0 };
|
||||||
let run_trap = |sig: Signal| -> ShResult<()> {
|
let run_trap = |sig: Signal| -> ShResult<()> {
|
||||||
if let Some(command) = read_logic(|l| l.get_trap(TrapTarget::Signal(sig))) {
|
if let Some(command) = read_logic(|l| l.get_trap(TrapTarget::Signal(sig))) {
|
||||||
exec_input(command, None, false)?;
|
exec_input(command, None, false, Some("trap".into()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
@@ -149,6 +149,22 @@ pub fn sig_setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset all signal dispositions to SIG_DFL.
|
||||||
|
/// Called in child processes before exec so that the shell's custom
|
||||||
|
/// handlers and SIG_IGN dispositions don't leak into child programs.
|
||||||
|
pub fn reset_signals() {
|
||||||
|
let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
|
||||||
|
unsafe {
|
||||||
|
for sig in Signal::iterator() {
|
||||||
|
// SIGKILL and SIGSTOP can't be caught/changed
|
||||||
|
if sig == Signal::SIGKILL || sig == Signal::SIGSTOP {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let _ = sigaction(sig, &default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn handle_signal(sig: libc::c_int) {
|
extern "C" fn handle_signal(sig: libc::c_int) {
|
||||||
SIGNALS.fetch_or(1 << sig, Ordering::SeqCst);
|
SIGNALS.fetch_or(1 << sig, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/state.rs
86
src/state.rs
@@ -20,7 +20,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
parse::{
|
parse::{
|
||||||
ConjunctNode, NdRule, Node, ParsedSrc,
|
ConjunctNode, NdRule, Node, ParsedSrc,
|
||||||
lex::{LexFlags, LexStream, Tk},
|
lex::{LexFlags, LexStream, Span, Tk},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
readline::{
|
readline::{
|
||||||
@@ -465,16 +465,31 @@ thread_local! {
|
|||||||
pub static SHED: Shed = Shed::new();
|
pub static SHED: Shed = Shed::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ShAlias {
|
||||||
|
pub body: String,
|
||||||
|
pub source: Span
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShAlias {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
///
|
///
|
||||||
/// Wraps the BraceGrp Node that forms the body of the function, and provides some helper methods to extract it from the parse tree
|
/// Wraps the BraceGrp Node that forms the body of the function, and provides some helper methods to extract it from the parse tree
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShFunc(Node);
|
pub struct ShFunc {
|
||||||
|
pub body: Node,
|
||||||
|
pub source: Span
|
||||||
|
}
|
||||||
|
|
||||||
impl ShFunc {
|
impl ShFunc {
|
||||||
pub fn new(mut src: ParsedSrc) -> Self {
|
pub fn new(mut src: ParsedSrc, source: Span) -> Self {
|
||||||
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
||||||
Self(body)
|
Self{ body, source }
|
||||||
}
|
}
|
||||||
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
||||||
// FIXME: find a better way to do this
|
// FIXME: find a better way to do this
|
||||||
@@ -487,10 +502,10 @@ impl ShFunc {
|
|||||||
*cmd
|
*cmd
|
||||||
}
|
}
|
||||||
pub fn body(&self) -> &Node {
|
pub fn body(&self) -> &Node {
|
||||||
&self.0
|
&self.body
|
||||||
}
|
}
|
||||||
pub fn body_mut(&mut self) -> &mut Node {
|
pub fn body_mut(&mut self) -> &mut Node {
|
||||||
&mut self.0
|
&mut self.body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +515,7 @@ impl ShFunc {
|
|||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct LogTab {
|
pub struct LogTab {
|
||||||
functions: HashMap<String, ShFunc>,
|
functions: HashMap<String, ShFunc>,
|
||||||
aliases: HashMap<String, String>,
|
aliases: HashMap<String, ShAlias>,
|
||||||
traps: HashMap<TrapTarget, String>,
|
traps: HashMap<TrapTarget, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,13 +544,13 @@ impl LogTab {
|
|||||||
pub fn funcs(&self) -> &HashMap<String, ShFunc> {
|
pub fn funcs(&self) -> &HashMap<String, ShFunc> {
|
||||||
&self.functions
|
&self.functions
|
||||||
}
|
}
|
||||||
pub fn aliases(&self) -> &HashMap<String, String> {
|
pub fn aliases(&self) -> &HashMap<String, ShAlias> {
|
||||||
&self.aliases
|
&self.aliases
|
||||||
}
|
}
|
||||||
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
pub fn insert_alias(&mut self, name: &str, body: &str, source: Span) {
|
||||||
self.aliases.insert(name.into(), body.into());
|
self.aliases.insert(name.into(), ShAlias { body: body.into(), source });
|
||||||
}
|
}
|
||||||
pub fn get_alias(&self, name: &str) -> Option<String> {
|
pub fn get_alias(&self, name: &str) -> Option<ShAlias> {
|
||||||
self.aliases.get(name).cloned()
|
self.aliases.get(name).cloned()
|
||||||
}
|
}
|
||||||
pub fn remove_alias(&mut self, name: &str) {
|
pub fn remove_alias(&mut self, name: &str) {
|
||||||
@@ -1140,6 +1155,29 @@ impl MetaTab {
|
|||||||
pub fn remove_comp_spec(&mut self, cmd: &str) -> bool {
|
pub fn remove_comp_spec(&mut self, cmd: &str) -> bool {
|
||||||
self.comp_specs.remove(cmd).is_some()
|
self.comp_specs.remove(cmd).is_some()
|
||||||
}
|
}
|
||||||
|
pub fn get_cmds_in_path() -> Vec<String> {
|
||||||
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
|
let paths = path.split(":").map(PathBuf::from);
|
||||||
|
let mut cmds = vec![];
|
||||||
|
for path in paths {
|
||||||
|
if let Ok(entries) = path.read_dir() {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||||
|
|
||||||
|
if meta.is_file()
|
||||||
|
&& is_exec
|
||||||
|
&& let Some(name) = entry.file_name().to_str()
|
||||||
|
{
|
||||||
|
cmds.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmds
|
||||||
|
}
|
||||||
pub fn try_rehash_commands(&mut self) {
|
pub fn try_rehash_commands(&mut self) {
|
||||||
let path = env::var("PATH").unwrap_or_default();
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
let cwd = env::var("PWD").unwrap_or_default();
|
let cwd = env::var("PWD").unwrap_or_default();
|
||||||
@@ -1155,25 +1193,10 @@ impl MetaTab {
|
|||||||
self.path_cache.clear();
|
self.path_cache.clear();
|
||||||
self.old_path = Some(path.clone());
|
self.old_path = Some(path.clone());
|
||||||
self.old_pwd = Some(cwd.clone());
|
self.old_pwd = Some(cwd.clone());
|
||||||
let paths = path.split(":").map(PathBuf::from);
|
let cmds_in_path = Self::get_cmds_in_path();
|
||||||
|
for cmd in cmds_in_path {
|
||||||
for path in paths {
|
self.path_cache.insert(cmd);
|
||||||
if let Ok(entries) = path.read_dir() {
|
}
|
||||||
for entry in entries.flatten() {
|
|
||||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
|
||||||
|
|
||||||
if meta.is_file()
|
|
||||||
&& is_exec
|
|
||||||
&& let Some(name) = entry.file_name().to_str()
|
|
||||||
{
|
|
||||||
self.path_cache.insert(name.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Ok(entries) = Path::new(&cwd).read_dir() {
|
if let Ok(entries) = Path::new(&cwd).read_dir() {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
@@ -1432,10 +1455,11 @@ pub fn source_rc() -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||||
|
let source_name = path.to_string_lossy().to_string();
|
||||||
let mut file = OpenOptions::new().read(true).open(path)?;
|
let mut file = OpenOptions::new().read(true).open(path)?;
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
file.read_to_string(&mut buf)?;
|
file.read_to_string(&mut buf)?;
|
||||||
exec_input(buf, None, false)?;
|
exec_input(buf, None, false, Some(source_name))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user