implemented 'type' and 'wait' builtins

fixed some tcsetpgrp() misbehavior

fixed not being able to redirect stderr from builtins
This commit is contained in:
2026-03-01 17:14:48 -05:00
parent 84aed128d6
commit 2ea44c55e9
38 changed files with 922 additions and 635 deletions

View File

@@ -1,17 +1,14 @@
use ariadne::Fmt;
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
state::{self, read_logic, write_logic},
};
use super::setup_builtin;
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn alias(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -20,8 +17,8 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if argv.is_empty() {
// 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 {
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);
Ok(())
}
pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn unalias(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -62,8 +59,8 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if argv.is_empty() {
// Display the environment variables

View File

@@ -1,13 +1,9 @@
use std::collections::VecDeque;
use ariadne::Span;
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> {
vec![
OptSpec {
@@ -44,7 +40,7 @@ impl Default for ArrOpOpts {
#[derive(Clone, Copy)]
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 {
assignments: _,
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 arr_op_opts = get_arr_op_opts(opts)?;
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let stdout = borrow_fd(STDOUT_FILENO);
let mut status = 0;
@@ -85,7 +81,7 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
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 NdRule::Command {
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 _arr_op_opts = get_arr_op_opts(opts)?;
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut argv = argv.into_iter();
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(())
}
pub fn arr_pop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
arr_pop_inner(node, io_stack, job, End::Back)
pub fn arr_pop(node: Node) -> ShResult<()> {
arr_pop_inner(node, End::Back)
}
pub fn arr_fpop(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
arr_pop_inner(node, io_stack, job, End::Front)
pub fn arr_fpop(node: Node) -> ShResult<()> {
arr_pop_inner(node, End::Front)
}
pub fn arr_push(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
arr_push_inner(node, io_stack, job, End::Back)
pub fn arr_push(node: Node) -> ShResult<()> {
arr_push_inner(node, End::Back)
}
pub fn arr_fpush(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
arr_push_inner(node, io_stack, job, End::Front)
pub fn arr_fpush(node: Node) -> ShResult<()> {
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 {
assignments: _,
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 arr_op_opts = get_arr_op_opts(opts)?;
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
for (arg, _) in argv {
write_vars(|v| -> ShResult<()> {

View File

@@ -2,16 +2,13 @@ use ariadne::Fmt;
use yansi::Color;
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
state::{self},
};
use super::setup_builtin;
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
pub fn cd(node: Node) -> ShResult<()> {
let span = node.get_span();
let NdRule::Command {
assignments: _,
@@ -22,8 +19,8 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
};
let cd_span = argv.first().unwrap().span.clone();
let (argv, _) = setup_builtin(Some(argv), job, None)?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
(PathBuf::from(arg),Some(span))

View File

@@ -2,12 +2,10 @@ use bitflags::bitflags;
use nix::{libc::STDOUT_FILENO, unistd::write};
use crate::{
builtin::setup_builtin,
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
procio::{IoStack, borrow_fd},
parse::{NdRule, Node, execute::prepare_argv},
procio::borrow_fd,
readline::complete::{BashCompSpec, CompContext, CompSpec},
state::{self, read_meta, write_meta},
};
@@ -149,7 +147,7 @@ pub struct CompOpts {
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 NdRule::Command {
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 comp_opts = get_comp_opts(opts)?;
let (argv, _) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if comp_opts.flags.contains(CompFlags::PRINT) {
if argv.is_empty() {
@@ -219,7 +217,7 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
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 NdRule::Command {
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 prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
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);

View File

@@ -5,11 +5,9 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
use yansi::Color;
use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
parse::{NdRule, Node, lex::Span},
procio::{IoStack, borrow_fd},
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
procio::borrow_fd,
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 NdRule::Command {
assignments: _,
@@ -113,8 +111,8 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut dir = None;
let mut rotate_idx = None;
@@ -184,7 +182,7 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
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 NdRule::Command {
assignments: _,
@@ -194,8 +192,8 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut remove_idx = None;
let mut no_cd = false;
@@ -276,7 +274,7 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
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 NdRule::Command {
assignments: _,
@@ -286,8 +284,8 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut abbreviate_home = true;
let mut one_per_line = false;

View File

@@ -1,12 +1,10 @@
use crate::{
builtin::setup_builtin,
expand::expand_prompt,
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
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 NdRule::Command {
assignments: _,
@@ -51,8 +49,8 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
assert!(!argv.is_empty());
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
let flags = get_echo_flags(opts).blame(blame)?;
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
borrow_fd(STDERR_FILENO)

View File

@@ -1,13 +1,10 @@
use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::ShResult,
parse::{NdRule, Node, execute::exec_input},
procio::IoStack,
parse::{NdRule, Node, execute::{exec_input, prepare_argv}},
state,
};
pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn eval(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -16,8 +13,8 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
unreachable!()
};
let (expanded_argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let expanded_argv = expanded_argv.unwrap();
let mut expanded_argv = prepare_argv(argv)?;
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
if expanded_argv.is_empty() {
state::set_status(0);
@@ -30,5 +27,5 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
.collect::<Vec<_>>()
.join(" ");
exec_input(joined_argv, None, false)
exec_input(joined_argv, None, false, Some("eval".into()))
}

View File

@@ -1,15 +1,12 @@
use nix::{errno::Errno, unistd::execvpe};
use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node, execute::ExecArgs},
procio::IoStack,
parse::{NdRule, Node, execute::{ExecArgs, prepare_argv}},
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 {
assignments: _,
argv,
@@ -18,13 +15,8 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
unreachable!()
};
let (expanded_argv, guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let expanded_argv = expanded_argv.unwrap();
if let Some(g) = guard {
// Persist redirections so they affect the entire shell,
// not just this command call
g.persist()
}
let mut expanded_argv = prepare_argv(argv)?;
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
if expanded_argv.is_empty() {
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();
match e {
Errno::ENOENT => Err(
ShErr::new(ShErrKind::CmdNotFound, span.clone())
ShErr::new(ShErrKind::NotFound, span.clone())
.labeled(span, format!("exec: command not found: {}", cmd_str))
),
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),

73
src/builtin/intro.rs Normal file
View 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(())
}

View File

@@ -1,22 +1,20 @@
use ariadne::Fmt;
use crate::{
jobs::{JobBldr, JobCmdFlags, JobID},
jobs::{JobCmdFlags, JobID, wait_bg},
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
parse::{NdRule, Node, lex::Span},
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
state::{self, read_jobs, write_jobs},
};
use super::setup_builtin;
pub enum JobBehavior {
Foregound,
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 cmd_tk = node.get_command();
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!()
};
let (argv, _) = setup_builtin(Some(argv), job, None)?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut argv = argv.into_iter();
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('%') {
let arg = arg.strip_prefix('%').unwrap();
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 {
let result = write_jobs(|j| {
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 {
assignments: _,
argv,
@@ -128,8 +131,8 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut flags = JobCmdFlags::empty();
for (arg, span) in argv {
@@ -158,7 +161,45 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
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 NdRule::Command {
assignments: _,
@@ -168,8 +209,8 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut argv = argv.into_iter();
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {

View File

@@ -5,7 +5,7 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
use serde_json::{Map, Value};
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)]
@@ -195,8 +195,6 @@ impl MapNode {
}
}
use super::setup_builtin;
fn map_opts_spec() -> [OptSpec; 6] {
[
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 {
assignments: _,
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 map_opts = get_map_opts(opts);
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
if !argv.is_empty() {
argv.remove(0); // remove "map" command from argv
}

View File

@@ -1,14 +1,5 @@
use nix::unistd::Pid;
use crate::{
jobs::{ChildProc, JobBldr},
libsh::error::ShResult,
parse::{
Redir,
execute::prepare_argv,
lex::{Span, Tk},
},
procio::{IoStack, RedirGuard},
state,
};
@@ -32,77 +23,15 @@ pub mod varcmds;
pub mod zoltraak;
pub mod map;
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",
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
"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<()> {
state::set_status(0);
Ok(())

View File

@@ -1,25 +1,20 @@
use crate::{
jobs::JobBldr,
libsh::error::ShResult,
parse::{NdRule, Node},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
state,
};
use super::setup_builtin;
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn pwd(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
argv: _,
} = node.class
else {
unreachable!()
};
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let stdout = borrow_fd(STDOUT_FILENO);
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();

View File

@@ -6,12 +6,10 @@ use nix::{
};
use crate::{
builtin::setup_builtin,
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
procio::{IoStack, borrow_fd},
parse::{NdRule, Node, execute::prepare_argv},
procio::borrow_fd,
readline::term::RawModeGuard,
state::{self, VarFlags, VarKind, read_vars, write_vars},
};
@@ -63,7 +61,7 @@ pub struct ReadOpts {
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 NdRule::Command {
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 read_opts = get_read_flags(opts).blame(blame.clone())?;
let (argv, _) = setup_builtin(Some(argv), job, None).blame(blame.clone())?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if let Some(prompt) = read_opts.prompt {
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;

View File

@@ -1,13 +1,10 @@
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
state::{self, write_vars},
};
use super::setup_builtin;
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
pub fn shift(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -16,8 +13,8 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
unreachable!()
};
let (argv, _) = setup_builtin(Some(argv), job, None)?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
let mut argv = argv.into_iter();
if let Some((arg, span)) = argv.next() {

View File

@@ -1,15 +1,12 @@
use crate::{
jobs::JobBldr,
libsh::error::{ShResult, ShResultExt},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
state::{self, write_shopts},
};
use super::setup_builtin;
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn shopt(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -18,8 +15,8 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if argv.is_empty() {
let mut output = write_shopts(|s| s.display_opts())?;

View File

@@ -1,14 +1,11 @@
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
state::{self, source_file},
};
use super::setup_builtin;
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
pub fn source(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -17,8 +14,8 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
unreachable!()
};
let (argv, _) = setup_builtin(Some(argv), job, None)?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
for (arg, span) in argv {
let path = PathBuf::from(arg);

View File

@@ -7,11 +7,9 @@ use nix::{
};
use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
procio::{IoStack, borrow_fd},
parse::{NdRule, Node, execute::prepare_argv},
procio::borrow_fd,
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 {
assignments: _,
argv,
@@ -123,8 +121,8 @@ pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if argv.is_empty() {
let stdout = borrow_fd(STDOUT_FILENO);

View File

@@ -1,15 +1,12 @@
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node, lex::split_tk_at},
parse::{NdRule, Node, execute::prepare_argv, lex::split_tk_at},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
state::{self, VarFlags, VarKind, read_vars, write_vars},
};
use super::setup_builtin;
pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn readonly(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -18,8 +15,6 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
unreachable!()
};
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
// Remove "readonly" from 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(())
}
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 NdRule::Command {
assignments: _,
@@ -71,8 +66,8 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
unreachable!()
};
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
let argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
if argv.is_empty() {
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(())
}
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn export(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -98,8 +93,6 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
unreachable!()
};
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
// Remove "export" from 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(())
}
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
pub fn local(node: Node) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
@@ -143,8 +136,6 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
unreachable!()
};
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
// Remove "local" from argv
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };

View File

@@ -2,15 +2,12 @@ use std::os::unix::fs::OpenOptionsExt;
use crate::{
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
procio::{IoStack, borrow_fd},
procio::borrow_fd,
};
use super::setup_builtin;
bitflags! {
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
struct ZoltFlags: u32 {
@@ -29,7 +26,7 @@ bitflags! {
/// 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
/// 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 {
assignments: _,
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 argv = argv.unwrap();
let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); }
for (arg, span) in argv {
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {