Added rustfmt.toml, formatted codebase

This commit is contained in:
2025-08-12 13:58:25 -04:00
parent d2b3cd51e0
commit 5aead4fcdc
52 changed files with 15188 additions and 14451 deletions

View File

@@ -1,95 +1,103 @@
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_logic, write_logic}};
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state::{self, read_logic, write_logic},
};
use super::setup_builtin;
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the environment variables
let mut alias_output = read_logic(|l| {
l.aliases()
.iter()
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
.collect::<Vec<_>>()
});
alias_output.sort(); // Sort them alphabetically
let mut alias_output = alias_output.join("\n"); // Join them with newlines
alias_output.push('\n'); // Push a final newline
if argv.is_empty() {
// Display the environment variables
let mut alias_output = read_logic(|l| {
l.aliases()
.iter()
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
.collect::<Vec<_>>()
});
alias_output.sort(); // Sort them alphabetically
let mut alias_output = alias_output.join("\n"); // Join them with newlines
alias_output.push('\n'); // Push a final newline
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, alias_output.as_bytes())?; // Write it
} else {
for (arg,span) in argv {
if arg == "command" || arg == "builtin" {
return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("alias: Cannot assign alias to reserved name '{arg}'"),
span
)
)
}
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, alias_output.as_bytes())?; // Write it
} else {
for (arg, span) in argv {
if arg == "command" || arg == "builtin" {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("alias: Cannot assign alias to reserved name '{arg}'"),
span,
));
}
let Some((name,body)) = arg.split_once('=') else {
return Err(
ShErr::full(
ShErrKind::SyntaxErr,
"alias: Expected an assignment in alias args",
span
)
)
};
write_logic(|l| l.insert_alias(name, body));
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
let Some((name, body)) = arg.split_once('=') else {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"alias: Expected an assignment in alias args",
span,
));
};
write_logic(|l| l.insert_alias(name, body));
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
}
pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the environment variables
let mut alias_output = read_logic(|l| {
l.aliases()
.iter()
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
.collect::<Vec<_>>()
});
alias_output.sort(); // Sort them alphabetically
let mut alias_output = alias_output.join("\n"); // Join them with newlines
alias_output.push('\n'); // Push a final newline
if argv.is_empty() {
// Display the environment variables
let mut alias_output = read_logic(|l| {
l.aliases()
.iter()
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
.collect::<Vec<_>>()
});
alias_output.sort(); // Sort them alphabetically
let mut alias_output = alias_output.join("\n"); // Join them with newlines
alias_output.push('\n'); // Push a final newline
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, alias_output.as_bytes())?; // Write it
} else {
for (arg,span) in argv {
flog!(DEBUG, arg);
if read_logic(|l| l.get_alias(&arg)).is_none() {
return Err(
ShErr::full(
ShErrKind::SyntaxErr,
format!("unalias: alias '{arg}' not found"),
span
)
)
};
write_logic(|l| l.remove_alias(&arg))
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, alias_output.as_bytes())?; // Write it
} else {
for (arg, span) in argv {
flog!(DEBUG, arg);
if read_logic(|l| l.get_alias(&arg)).is_none() {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
format!("unalias: alias '{arg}' not found"),
span,
));
};
write_logic(|l| l.remove_alias(&arg))
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
}

View File

@@ -1,45 +1,51 @@
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, state::{self}};
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
prelude::*,
state::{self},
};
use super::setup_builtin;
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
let span = node.get_span();
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let span = node.get_span();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,_) = setup_builtin(argv,job,None)?;
let (argv, _) = setup_builtin(argv, job, None)?;
let new_dir = if let Some((arg,_)) = argv.into_iter().next() {
PathBuf::from(arg)
} else {
PathBuf::from(env::var("HOME").unwrap())
};
let new_dir = if let Some((arg, _)) = argv.into_iter().next() {
PathBuf::from(arg)
} else {
PathBuf::from(env::var("HOME").unwrap())
};
if !new_dir.exists() {
return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("cd: No such file or directory '{}'",new_dir.display()),
span,
)
)
}
if !new_dir.exists() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("cd: No such file or directory '{}'", new_dir.display()),
span,
));
}
if !new_dir.is_dir() {
return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("cd: Not a directory '{}'",new_dir.display()),
span,
)
)
}
if !new_dir.is_dir() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("cd: Not a directory '{}'", new_dir.display()),
span,
));
}
env::set_current_dir(new_dir).unwrap();
let new_dir = env::current_dir().unwrap();
env::set_var("PWD", new_dir);
env::set_current_dir(new_dir).unwrap();
let new_dir = env::current_dir().unwrap();
env::set_var("PWD", new_dir);
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}

View File

@@ -1,77 +1,90 @@
use std::sync::LazyLock;
use crate::{builtin::setup_builtin, getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
use crate::{
builtin::setup_builtin,
getopt::{get_opts_from_tokens, Opt, OptSet},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state,
};
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {[
Opt::Short('n'),
Opt::Short('E'),
Opt::Short('e'),
Opt::Short('r'),
].into()});
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
[
Opt::Short('n'),
Opt::Short('E'),
Opt::Short('e'),
Opt::Short('r'),
]
.into()
});
bitflags! {
pub struct EchoFlags: u32 {
const NO_NEWLINE = 0b000001;
const USE_STDERR = 0b000010;
const USE_ESCAPE = 0b000100;
}
pub struct EchoFlags: u32 {
const NO_NEWLINE = 0b000001;
const USE_STDERR = 0b000010;
const USE_ESCAPE = 0b000100;
}
}
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
assert!(!argv.is_empty());
let (argv,opts) = get_opts_from_tokens(argv);
let flags = get_echo_flags(opts).blame(blame)?;
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
assert!(!argv.is_empty());
let (argv, opts) = get_opts_from_tokens(argv);
let flags = get_echo_flags(opts).blame(blame)?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
borrow_fd(STDERR_FILENO)
} else {
borrow_fd(STDOUT_FILENO)
};
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
borrow_fd(STDERR_FILENO)
} else {
borrow_fd(STDOUT_FILENO)
};
let mut echo_output = argv.into_iter()
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
.collect::<Vec<_>>()
.join(" ");
let mut echo_output = argv
.into_iter()
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
.collect::<Vec<_>>()
.join(" ");
if !flags.contains(EchoFlags::NO_NEWLINE) {
echo_output.push('\n')
}
if !flags.contains(EchoFlags::NO_NEWLINE) {
echo_output.push('\n')
}
write(output_channel, echo_output.as_bytes())?;
write(output_channel, echo_output.as_bytes())?;
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
}
pub fn get_echo_flags(mut opts: Vec<Opt>) -> ShResult<EchoFlags> {
let mut flags = EchoFlags::empty();
let mut flags = EchoFlags::empty();
while let Some(opt) = opts.pop() {
if !ECHO_OPTS.contains(&opt) {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
format!("echo: Unexpected flag '{opt}'"),
)
)
}
let Opt::Short(opt) = opt else {
unreachable!()
};
while let Some(opt) = opts.pop() {
if !ECHO_OPTS.contains(&opt) {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("echo: Unexpected flag '{opt}'"),
));
}
let Opt::Short(opt) = opt else { unreachable!() };
match opt {
'n' => flags |= EchoFlags::NO_NEWLINE,
'r' => flags |= EchoFlags::USE_STDERR,
'e' => flags |= EchoFlags::USE_ESCAPE,
_ => unreachable!()
}
}
match opt {
'n' => flags |= EchoFlags::NO_NEWLINE,
'r' => flags |= EchoFlags::USE_STDERR,
'e' => flags |= EchoFlags::USE_ESCAPE,
_ => unreachable!(),
}
}
Ok(flags)
Ok(flags)
}

View File

@@ -1,35 +1,48 @@
use crate::{jobs::JobBldr, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, write_vars}};
use crate::{
jobs::JobBldr,
libsh::error::ShResult,
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state::{self, write_vars},
};
use super::setup_builtin;
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the environment variables
let mut env_output = env::vars()
.map(|var| format!("{}={}",var.0,var.1)) // Get all of them, zip them into one string
.collect::<Vec<_>>();
env_output.sort(); // Sort them alphabetically
let mut env_output = env_output.join("\n"); // Join them with newlines
env_output.push('\n'); // Push a final newline
if argv.is_empty() {
// Display the environment variables
let mut env_output = env::vars()
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
.collect::<Vec<_>>();
env_output.sort(); // Sort them alphabetically
let mut env_output = env_output.join("\n"); // Join them with newlines
env_output.push('\n'); // Push a final newline
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, env_output.as_bytes())?; // Write it
} else {
for (arg,_) in argv {
if let Some((var,val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, true)); // Export an assignment like 'foo=bar'
} else {
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if any
}
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, env_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, true)); // Export an assignment like
// 'foo=bar'
} else {
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
// any
}
}
}
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
}

View File

@@ -1,39 +1,46 @@
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*};
use crate::{
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{execute::prepare_argv, NdRule, Node},
prelude::*,
};
pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
use ShErrKind::*;
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let mut code = 0;
use ShErrKind::*;
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let mut code = 0;
let mut argv = prepare_argv(argv)?;
let cmd = argv.remove(0).0;
let mut argv = prepare_argv(argv)?;
let cmd = argv.remove(0).0;
if !argv.is_empty() {
let (arg,span) = argv
.into_iter()
.next()
.unwrap();
if !argv.is_empty() {
let (arg, span) = argv.into_iter().next().unwrap();
let Ok(status) = arg.parse::<i32>() else {
return Err(
ShErr::full(ShErrKind::SyntaxErr, format!("{cmd}: Expected a number"), span)
)
};
let Ok(status) = arg.parse::<i32>() else {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
format!("{cmd}: Expected a number"),
span,
));
};
code = status;
}
code = status;
}
flog!(DEBUG,code);
flog!(DEBUG, code);
let kind = match kind {
LoopContinue(_) => LoopContinue(code),
LoopBreak(_) => LoopBreak(code),
FuncReturn(_) => FuncReturn(code),
CleanExit(_) => CleanExit(code),
_ => unreachable!()
};
let kind = match kind {
LoopContinue(_) => LoopContinue(code),
LoopBreak(_) => LoopBreak(code),
FuncReturn(_) => FuncReturn(code),
CleanExit(_) => CleanExit(code),
_ => unreachable!(),
};
Err(ShErr::simple(kind, ""))
Err(ShErr::simple(kind, ""))
}

View File

@@ -1,183 +1,182 @@
use crate::{jobs::{JobBldr, JobCmdFlags, JobID}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
use crate::{
jobs::{JobBldr, JobCmdFlags, JobID},
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{lex::Span, NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state::{self, read_jobs, write_jobs},
};
use super::setup_builtin;
pub enum JobBehavior {
Foregound,
Background
Foregound,
Background,
}
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
let blame = node.get_span().clone();
let cmd = match behavior {
JobBehavior::Foregound => "fg",
JobBehavior::Background => "bg"
};
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let blame = node.get_span().clone();
let cmd = match behavior {
JobBehavior::Foregound => "fg",
JobBehavior::Background => "bg",
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,_) = setup_builtin(argv, job, None)?;
let mut argv = argv.into_iter();
let (argv, _) = setup_builtin(argv, job, None)?;
let mut argv = argv.into_iter();
if read_jobs(|j| j.get_fg().is_some()) {
return Err(
ShErr::full(
ShErrKind::InternalErr,
format!("Somehow called '{}' with an existing foreground job",cmd),
blame
)
)
}
if read_jobs(|j| j.get_fg().is_some()) {
return Err(ShErr::full(
ShErrKind::InternalErr,
format!("Somehow called '{}' with an existing foreground job", cmd),
blame,
));
}
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
id
} else {
return Err(
ShErr::full(
ShErrKind::ExecFail,
"No jobs found",
blame
)
)
};
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
id
} else {
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame));
};
let tabid = match argv.next() {
Some((arg,blame)) => parse_job_id(&arg, blame)?,
None => curr_job_id
};
let tabid = match argv.next() {
Some((arg, blame)) => parse_job_id(&arg, blame)?,
None => curr_job_id,
};
let mut job = write_jobs(|j| {
let id = JobID::TableID(tabid);
let query_result = j.query(id.clone());
if query_result.is_some() {
Ok(j.remove_job(id.clone()).unwrap())
} else {
Err(
ShErr::full(
ShErrKind::ExecFail,
format!("Job id `{}' not found", tabid),
blame
)
)
}
})?;
let mut job = write_jobs(|j| {
let id = JobID::TableID(tabid);
let query_result = j.query(id.clone());
if query_result.is_some() {
Ok(j.remove_job(id.clone()).unwrap())
} else {
Err(ShErr::full(
ShErrKind::ExecFail,
format!("Job id `{}' not found", tabid),
blame,
))
}
})?;
job.killpg(Signal::SIGCONT)?;
job.killpg(Signal::SIGCONT)?;
match behavior {
JobBehavior::Foregound => {
write_jobs(|j| j.new_fg(job))?;
}
JobBehavior::Background => {
let job_order = read_jobs(|j| j.order().to_vec());
write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?;
write_jobs(|j| j.insert_job(job, true))?;
}
}
state::set_status(0);
Ok(())
match behavior {
JobBehavior::Foregound => {
write_jobs(|j| j.new_fg(job))?;
}
JobBehavior::Background => {
let job_order = read_jobs(|j| j.order().to_vec());
write(
borrow_fd(1),
job.display(&job_order, JobCmdFlags::PIDS).as_bytes(),
)?;
write_jobs(|j| j.insert_job(job, true))?;
}
}
state::set_status(0);
Ok(())
}
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())
} else {
let result = write_jobs(|j| {
let query_result = j.query(JobID::Command(arg.into()));
query_result.map(|job| job.tabid().unwrap())
});
match result {
Some(id) => Ok(id),
None => Err(
ShErr::full(
ShErrKind::InternalErr,
"Found a job but no table id in parse_job_id()",
blame
)
)
}
}
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
let result = write_jobs(|j| {
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
if let Some(job) = pgid_query_result {
return Some(job.tabid().unwrap())
}
if arg.starts_with('%') {
let arg = arg.strip_prefix('%').unwrap();
if arg.chars().all(|ch| ch.is_ascii_digit()) {
Ok(arg.parse::<usize>().unwrap())
} else {
let result = write_jobs(|j| {
let query_result = j.query(JobID::Command(arg.into()));
query_result.map(|job| job.tabid().unwrap())
});
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(
ShErrKind::InternalErr,
"Found a job but no table id in parse_job_id()",
blame,
)),
}
}
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
let result = write_jobs(|j| {
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
if let Some(job) = pgid_query_result {
return Some(job.tabid().unwrap());
}
if arg.parse::<i32>().unwrap() > 0 {
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
return table_id_query_result.map(|job| job.tabid().unwrap());
}
if arg.parse::<i32>().unwrap() > 0 {
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
return table_id_query_result.map(|job| job.tabid().unwrap());
}
None
});
None
});
match result {
Some(id) => Ok(id),
None => Err(
ShErr::full(
ShErrKind::InternalErr,
"Found a job but no table id in parse_job_id()",
blame
)
)
}
} else {
Err(
ShErr::full(
ShErrKind::SyntaxErr,
format!("Invalid fd arg: {}", arg),
blame
)
)
}
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(
ShErrKind::InternalErr,
"Found a job but no table id in parse_job_id()",
blame,
)),
}
} else {
Err(ShErr::full(
ShErrKind::SyntaxErr,
format!("Invalid fd arg: {}", arg),
blame,
))
}
}
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut flags = JobCmdFlags::empty();
for (arg,span) in argv {
let mut chars = arg.chars().peekable();
if chars.peek().is_none_or(|ch| *ch != '-') {
return Err(
ShErr::full(
ShErrKind::SyntaxErr,
"Invalid flag in jobs call",
span
)
)
}
chars.next();
for ch in chars {
let flag = match ch {
'l' => JobCmdFlags::LONG,
'p' => JobCmdFlags::PIDS,
'n' => JobCmdFlags::NEW_ONLY,
'r' => JobCmdFlags::RUNNING,
's' => JobCmdFlags::STOPPED,
_ => return Err(
ShErr::full(
ShErrKind::SyntaxErr,
"Invalid flag in jobs call",
span
)
)
let mut flags = JobCmdFlags::empty();
for (arg, span) in argv {
let mut chars = arg.chars().peekable();
if chars.peek().is_none_or(|ch| *ch != '-') {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"Invalid flag in jobs call",
span,
));
}
chars.next();
for ch in chars {
let flag = match ch {
'l' => JobCmdFlags::LONG,
'p' => JobCmdFlags::PIDS,
'n' => JobCmdFlags::NEW_ONLY,
'r' => JobCmdFlags::RUNNING,
's' => JobCmdFlags::STOPPED,
_ => {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"Invalid flag in jobs call",
span,
))
}
};
flags |= flag
}
}
write_jobs(|j| j.print_jobs(flags))?;
io_frame.unwrap().restore()?;
state::set_status(0);
};
flags |= flag
}
}
write_jobs(|j| j.print_jobs(flags))?;
io_frame.unwrap().restore()?;
state::set_status(0);
Ok(())
Ok(())
}

View File

@@ -1,50 +1,45 @@
use nix::unistd::Pid;
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, lex::{Span, Tk}, Redir}, procio::{IoFrame, IoStack}};
use crate::{
jobs::{ChildProc, JobBldr},
libsh::error::ShResult,
parse::{
execute::prepare_argv,
lex::{Span, Tk},
Redir,
},
procio::{IoFrame, IoStack},
};
pub mod echo;
pub mod cd;
pub mod export;
pub mod pwd;
pub mod source;
pub mod shift;
pub mod jobctl;
pub mod alias;
pub mod cd;
pub mod echo;
pub mod export;
pub mod flowctl;
pub mod zoltraak;
pub mod jobctl;
pub mod pwd;
pub mod shift;
pub mod shopt;
pub mod test; // [[ ]] thing
pub mod source;
pub mod test;
pub mod zoltraak; // [[ ]] thing
pub const BUILTINS: [&str;19] = [
"echo",
"cd",
"export",
"pwd",
"source",
"shift",
"jobs",
"fg",
"bg",
"alias",
"unalias",
"return",
"break",
"continue",
"exit",
"zoltraak",
"shopt",
"builtin",
"command",
pub const BUILTINS: [&str; 19] = [
"echo", "cd", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias",
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command",
];
/// 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`
/// 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`
/// * 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
@@ -56,36 +51,39 @@ pub const BUILTINS: [&str;19] = [
/// * 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 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<(Vec<(String,Span)>, Option<IoFrame>)>;
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<IoFrame>)>;
pub fn setup_builtin(
argv: Vec<Tk>,
job: &mut JobBldr,
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
argv: Vec<Tk>,
job: &mut JobBldr,
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
) -> SetupReturns {
let mut argv: Vec<(String,Span)> = prepare_argv(argv)?;
let mut argv: Vec<(String, Span)> = prepare_argv(argv)?;
let child_pgid = if let Some(pgid) = job.pgid() {
pgid
} else {
job.set_pgid(Pid::this());
Pid::this()
};
let cmd_name = argv.remove(0).0;
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
job.push_child(child);
let child_pgid = if let Some(pgid) = job.pgid() {
pgid
} else {
job.set_pgid(Pid::this());
Pid::this()
};
let cmd_name = argv.remove(0).0;
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
job.push_child(child);
let io_frame = if let Some((io_stack,redirs)) = io_mode {
io_stack.append_to_frame(redirs);
let mut io_frame = io_stack.pop_frame();
io_frame.redirect()?;
Some(io_frame)
} else {
None
};
let io_frame = if let Some((io_stack, redirs)) = io_mode {
io_stack.append_to_frame(redirs);
let mut io_frame = io_stack.pop_frame();
io_frame.redirect()?;
Some(io_frame)
} else {
None
};
// We return the io_frame because the caller needs to also call io_frame.restore()
Ok((argv,io_frame))
// We return the io_frame because the caller needs to also call
// io_frame.restore()
Ok((argv, io_frame))
}

View File

@@ -1,21 +1,32 @@
use crate::{jobs::JobBldr, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
use crate::{
jobs::JobBldr,
libsh::error::ShResult,
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state,
};
use super::setup_builtin;
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (_,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let (_, io_frame) = setup_builtin(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();
curr_dir.push('\n');
write(stdout, curr_dir.as_bytes())?;
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
curr_dir.push('\n');
write(stdout, curr_dir.as_bytes())?;
io_frame.unwrap().restore().unwrap();
state::set_status(0);
Ok(())
io_frame.unwrap().restore().unwrap();
state::set_status(0);
Ok(())
}

View File

@@ -1,30 +1,37 @@
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, state::{self, write_vars}};
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
state::{self, write_vars},
};
use super::setup_builtin;
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,_) = setup_builtin(argv, job, None)?;
let mut argv = argv.into_iter();
let (argv, _) = setup_builtin(argv, job, None)?;
let mut argv = argv.into_iter();
if let Some((arg,span)) = argv.next() {
let Ok(count) = arg.parse::<usize>() else {
return Err(
ShErr::full(
ShErrKind::ExecFail,
"Expected a number in shift args",
span
)
)
};
for _ in 0..count {
write_vars(|v| v.fpop_arg());
}
}
if let Some((arg, span)) = argv.next() {
let Ok(count) = arg.parse::<usize>() else {
return Err(ShErr::full(
ShErrKind::ExecFail,
"Expected a number in shift args",
span,
));
};
for _ in 0..count {
write_vars(|v| v.fpop_arg());
}
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}

View File

@@ -1,30 +1,41 @@
use crate::{jobs::JobBldr, libsh::error::{ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::write_shopts};
use crate::{
jobs::JobBldr,
libsh::error::{ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state::write_shopts,
};
use super::setup_builtin;
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut io_frame = io_frame.unwrap();
io_frame.redirect()?;
for (arg,span) in argv {
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
continue
};
let mut io_frame = io_frame.unwrap();
io_frame.redirect()?;
for (arg, span) in argv {
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
continue;
};
let output_channel = borrow_fd(STDOUT_FILENO);
output.push('\n');
let output_channel = borrow_fd(STDOUT_FILENO);
output.push('\n');
if let Err(e) = write(output_channel, output.as_bytes()) {
io_frame.restore()?;
return Err(e.into())
}
}
io_frame.restore()?;
if let Err(e) = write(output_channel, output.as_bytes()) {
io_frame.restore()?;
return Err(e.into());
}
}
io_frame.restore()?;
Ok(())
Ok(())
}

View File

@@ -1,37 +1,43 @@
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, state::{self, source_file}};
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
prelude::*,
state::{self, source_file},
};
use super::setup_builtin;
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv,_) = setup_builtin(argv, job, None)?;
let (argv, _) = setup_builtin(argv, job, None)?;
for (arg,span) in argv {
let path = PathBuf::from(arg);
if !path.exists() {
return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("source: File '{}' not found",path.display()),
span
)
);
}
if !path.is_file() {
return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("source: Given path '{}' is not a file",path.display()),
span
)
);
}
source_file(path)?;
}
for (arg, span) in argv {
let path = PathBuf::from(arg);
if !path.exists() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("source: File '{}' not found", path.display()),
span,
));
}
if !path.is_file() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("source: Given path '{}' is not a file", path.display()),
span,
));
}
source_file(path)?;
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}

View File

@@ -1,328 +1,321 @@
use std::{fs::metadata, path::PathBuf, str::FromStr};
use nix::{sys::stat::{self, SFlag}, unistd::AccessFlags};
use nix::{
sys::stat::{self, SFlag},
unistd::AccessFlags,
};
use regex::Regex;
use crate::{libsh::error::{ShErr, ShErrKind, ShResult},prelude::*, parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS}};
use crate::{
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS},
prelude::*,
};
#[derive(Debug, Clone)]
pub enum UnaryOp {
Exists, // -e
Directory, // -d
File, // -f
Symlink, // -h or -L
Readable, // -r
Writable, // -w
Executable, // -x
NonEmpty, // -s
NamedPipe, // -p
Socket, // -S
BlockSpecial, // -b
CharSpecial, // -c
Sticky, // -k
UIDOwner, // -O
GIDOwner, // -G
ModifiedSinceStatusChange, // -N
SetUID, // -u
SetGID, // -g
Terminal, // -t
NonNull, // -n
Null, // -z
Exists, // -e
Directory, // -d
File, // -f
Symlink, // -h or -L
Readable, // -r
Writable, // -w
Executable, // -x
NonEmpty, // -s
NamedPipe, // -p
Socket, // -S
BlockSpecial, // -b
CharSpecial, // -c
Sticky, // -k
UIDOwner, // -O
GIDOwner, // -G
ModifiedSinceStatusChange, // -N
SetUID, // -u
SetGID, // -g
Terminal, // -t
NonNull, // -n
Null, // -z
}
impl FromStr for UnaryOp {
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"-e" => Ok(Self::Exists),
"-d" => Ok(Self::Directory),
"-f" => Ok(Self::File),
"-h" | "-L" => Ok(Self::Symlink), // -h or -L
"-r" => Ok(Self::Readable),
"-w" => Ok(Self::Writable),
"-x" => Ok(Self::Executable),
"-s" => Ok(Self::NonEmpty),
"-p" => Ok(Self::NamedPipe),
"-S" => Ok(Self::Socket),
"-b" => Ok(Self::BlockSpecial),
"-c" => Ok(Self::CharSpecial),
"-k" => Ok(Self::Sticky),
"-O" => Ok(Self::UIDOwner),
"-G" => Ok(Self::GIDOwner),
"-N" => Ok(Self::ModifiedSinceStatusChange),
"-u" => Ok(Self::SetUID),
"-g" => Ok(Self::SetGID),
"-t" => Ok(Self::Terminal),
"-n" => Ok(Self::NonNull),
"-z" => Ok(Self::Null),
_ => Err(ShErr::Simple { kind: ShErrKind::SyntaxErr, msg: "Invalid test operator".into(), notes: vec![] })
}
}
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"-e" => Ok(Self::Exists),
"-d" => Ok(Self::Directory),
"-f" => Ok(Self::File),
"-h" | "-L" => Ok(Self::Symlink), // -h or -L
"-r" => Ok(Self::Readable),
"-w" => Ok(Self::Writable),
"-x" => Ok(Self::Executable),
"-s" => Ok(Self::NonEmpty),
"-p" => Ok(Self::NamedPipe),
"-S" => Ok(Self::Socket),
"-b" => Ok(Self::BlockSpecial),
"-c" => Ok(Self::CharSpecial),
"-k" => Ok(Self::Sticky),
"-O" => Ok(Self::UIDOwner),
"-G" => Ok(Self::GIDOwner),
"-N" => Ok(Self::ModifiedSinceStatusChange),
"-u" => Ok(Self::SetUID),
"-g" => Ok(Self::SetGID),
"-t" => Ok(Self::Terminal),
"-n" => Ok(Self::NonNull),
"-z" => Ok(Self::Null),
_ => Err(ShErr::Simple {
kind: ShErrKind::SyntaxErr,
msg: "Invalid test operator".into(),
notes: vec![],
}),
}
}
}
#[derive(Debug, Clone)]
pub enum TestOp {
Unary(UnaryOp),
StringEq, // ==
StringNeq, // !=
IntEq, // -eq
IntNeq, // -ne
IntGt, // -gt
IntLt, // -lt
IntGe, // -ge
IntLe, // -le
RegexMatch, // =~
Unary(UnaryOp),
StringEq, // ==
StringNeq, // !=
IntEq, // -eq
IntNeq, // -ne
IntGt, // -gt
IntLt, // -lt
IntGe, // -ge
IntLe, // -le
RegexMatch, // =~
}
impl FromStr for TestOp {
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"==" => Ok(Self::StringEq),
"!=" => Ok(Self::StringNeq),
"=~" => Ok(Self::RegexMatch),
"-eq" => Ok(Self::IntEq),
"-ne" => Ok(Self::IntNeq),
"-gt" => Ok(Self::IntGt),
"-lt" => Ok(Self::IntLt),
"-ge" => Ok(Self::IntGe),
"-le" => Ok(Self::IntLe),
_ if TEST_UNARY_OPS.contains(&s) => {
Ok(Self::Unary(s.parse::<UnaryOp>()?))
}
_ => Err(ShErr::Simple { kind: ShErrKind::SyntaxErr, msg: "Invalid test operator".into(), notes: vec![] })
}
}
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"==" => Ok(Self::StringEq),
"!=" => Ok(Self::StringNeq),
"=~" => Ok(Self::RegexMatch),
"-eq" => Ok(Self::IntEq),
"-ne" => Ok(Self::IntNeq),
"-gt" => Ok(Self::IntGt),
"-lt" => Ok(Self::IntLt),
"-ge" => Ok(Self::IntGe),
"-le" => Ok(Self::IntLe),
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
_ => Err(ShErr::Simple {
kind: ShErrKind::SyntaxErr,
msg: "Invalid test operator".into(),
notes: vec![],
}),
}
}
}
fn replace_posix_classes(pat: &str) -> String {
pat.replace("[[:alnum:]]", r"[A-Za-z0-9]")
.replace("[[:alpha:]]", r"[A-Za-z]")
.replace("[[:blank:]]", r"[ \t]")
.replace("[[:cntrl:]]", r"[\x00-\x1F\x7F]")
.replace("[[:digit:]]", r"[0-9]")
.replace("[[:graph:]]", r"[!-~]")
.replace("[[:lower:]]", r"[a-z]")
.replace("[[:print:]]", r"[\x20-\x7E]")
.replace("[[:space:]]", r"[ \t\r\n\x0B\x0C]") // vertical tab (\x0B), form feed (\x0C)
.replace("[[:upper:]]", r"[A-Z]")
.replace("[[:xdigit:]]", r"[0-9A-Fa-f]")
pat
.replace("[[:alnum:]]", r"[A-Za-z0-9]")
.replace("[[:alpha:]]", r"[A-Za-z]")
.replace("[[:blank:]]", r"[ \t]")
.replace("[[:cntrl:]]", r"[\x00-\x1F\x7F]")
.replace("[[:digit:]]", r"[0-9]")
.replace("[[:graph:]]", r"[!-~]")
.replace("[[:lower:]]", r"[a-z]")
.replace("[[:print:]]", r"[\x20-\x7E]")
.replace("[[:space:]]", r"[ \t\r\n\x0B\x0C]") // vertical tab (\x0B), form feed (\x0C)
.replace("[[:upper:]]", r"[A-Z]")
.replace("[[:xdigit:]]", r"[0-9A-Fa-f]")
}
pub fn double_bracket_test(node: Node) -> ShResult<bool> {
let err_span = node.get_span();
let NdRule::Test { cases } = node.class else {
unreachable!()
};
let mut last_result = false;
let mut conjunct_op: Option<ConjunctOp>;
let err_span = node.get_span();
let NdRule::Test { cases } = node.class else {
unreachable!()
};
let mut last_result = false;
let mut conjunct_op: Option<ConjunctOp>;
for case in cases {
let result = match case {
TestCase::Unary { operator, operand, conjunct } => {
let operand = operand.expand()?.get_words().join(" ");
conjunct_op = conjunct;
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
return Err(
ShErr::Full { kind: ShErrKind::SyntaxErr, msg: "Invalid unary operator".into(), notes: vec![], span: err_span }
)
};
match op {
UnaryOp::Exists => {
let path = PathBuf::from(operand.as_str());
path.exists()
}
UnaryOp::Directory => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata()
.unwrap()
.is_dir()
} else {
false
}
}
UnaryOp::File => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata()
.unwrap()
.is_file()
} else {
false
}
}
UnaryOp::Symlink => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata()
.unwrap()
.file_type()
.is_symlink()
} else {
false
}
}
UnaryOp::Readable => nix::unistd::access(operand.as_str(), AccessFlags::R_OK).is_ok(),
UnaryOp::Writable => nix::unistd::access(operand.as_str(), AccessFlags::W_OK).is_ok(),
UnaryOp::Executable => nix::unistd::access(operand.as_str(), AccessFlags::X_OK).is_ok(),
UnaryOp::NonEmpty => {
match metadata(operand.as_str()) {
Ok(meta) => meta.len() > 0,
Err(_) => false
}
}
UnaryOp::NamedPipe => {
match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO),
Err(_) => false,
}
}
UnaryOp::Socket => {
match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK),
Err(_) => false,
}
}
UnaryOp::BlockSpecial => {
match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFBLK),
Err(_) => false,
}
}
UnaryOp::CharSpecial => {
match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFCHR),
Err(_) => false,
}
}
UnaryOp::Sticky => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISVTX != 0,
Err(_) => false,
}
}
UnaryOp::UIDOwner => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_uid == nix::unistd::geteuid().as_raw(),
Err(_) => false,
}
}
for case in cases {
let result = match case {
TestCase::Unary {
operator,
operand,
conjunct,
} => {
let operand = operand.expand()?.get_words().join(" ");
conjunct_op = conjunct;
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
return Err(ShErr::Full {
kind: ShErrKind::SyntaxErr,
msg: "Invalid unary operator".into(),
notes: vec![],
span: err_span,
});
};
match op {
UnaryOp::Exists => {
let path = PathBuf::from(operand.as_str());
path.exists()
}
UnaryOp::Directory => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata().unwrap().is_dir()
} else {
false
}
}
UnaryOp::File => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata().unwrap().is_file()
} else {
false
}
}
UnaryOp::Symlink => {
let path = PathBuf::from(operand.as_str());
if path.exists() {
path.metadata().unwrap().file_type().is_symlink()
} else {
false
}
}
UnaryOp::Readable => nix::unistd::access(operand.as_str(), AccessFlags::R_OK).is_ok(),
UnaryOp::Writable => nix::unistd::access(operand.as_str(), AccessFlags::W_OK).is_ok(),
UnaryOp::Executable => nix::unistd::access(operand.as_str(), AccessFlags::X_OK).is_ok(),
UnaryOp::NonEmpty => match metadata(operand.as_str()) {
Ok(meta) => meta.len() > 0,
Err(_) => false,
},
UnaryOp::NamedPipe => match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO),
Err(_) => false,
},
UnaryOp::Socket => match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK),
Err(_) => false,
},
UnaryOp::BlockSpecial => match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFBLK),
Err(_) => false,
},
UnaryOp::CharSpecial => match stat::stat(operand.as_str()) {
Ok(stat) => SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFCHR),
Err(_) => false,
},
UnaryOp::Sticky => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISVTX != 0,
Err(_) => false,
},
UnaryOp::UIDOwner => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_uid == nix::unistd::geteuid().as_raw(),
Err(_) => false,
},
UnaryOp::GIDOwner => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_gid == nix::unistd::getegid().as_raw(),
Err(_) => false,
}
}
UnaryOp::GIDOwner => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_gid == nix::unistd::getegid().as_raw(),
Err(_) => false,
},
UnaryOp::ModifiedSinceStatusChange => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mtime > stat.st_ctime,
Err(_) => false,
}
}
UnaryOp::ModifiedSinceStatusChange => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mtime > stat.st_ctime,
Err(_) => false,
},
UnaryOp::SetUID => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISUID != 0,
Err(_) => false,
}
}
UnaryOp::SetUID => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISUID != 0,
Err(_) => false,
},
UnaryOp::SetGID => {
match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISGID != 0,
Err(_) => false,
}
}
UnaryOp::SetGID => match stat::stat(operand.as_str()) {
Ok(stat) => stat.st_mode & nix::libc::S_ISGID != 0,
Err(_) => false,
},
UnaryOp::Terminal => {
match operand.as_str().parse::<nix::libc::c_int>() {
Ok(fd) => unsafe { nix::libc::isatty(fd) == 1 },
Err(_) => false,
}
}
UnaryOp::NonNull => !operand.is_empty(),
UnaryOp::Null => operand.is_empty(),
}
}
TestCase::Binary { lhs, operator, rhs, conjunct } => {
let lhs = lhs.expand()?.get_words().join(" ");
let rhs = rhs.expand()?.get_words().join(" ");
conjunct_op = conjunct;
let test_op = operator.as_str().parse::<TestOp>()?;
flog!(DEBUG, lhs);
flog!(DEBUG, rhs);
flog!(DEBUG, test_op);
match test_op {
TestOp::Unary(_) => {
return Err(
ShErr::Full {
kind: ShErrKind::SyntaxErr,
msg: "Expected a binary operator in this test call; found a unary operator".into(),
notes: vec![],
span: err_span
}
)
}
TestOp::StringEq => rhs.trim() == lhs.trim(),
TestOp::StringNeq => rhs.trim() != lhs.trim(),
TestOp::IntNeq |
TestOp::IntGt |
TestOp::IntLt |
TestOp::IntGe |
TestOp::IntLe |
TestOp::IntEq => {
let err = ShErr::Full {
kind: ShErrKind::SyntaxErr,
msg: format!("Expected an integer with '{}' operator", operator.as_str()),
notes: vec![],
span: err_span.clone()
};
let Ok(lhs) = lhs.trim().parse::<i32>() else {
return Err(err)
};
let Ok(rhs) = rhs.trim().parse::<i32>() else {
return Err(err)
};
match test_op {
TestOp::IntNeq => lhs != rhs,
TestOp::IntGt => lhs > rhs,
TestOp::IntLt => lhs < rhs,
TestOp::IntGe => lhs >= rhs,
TestOp::IntLe => lhs <= rhs,
TestOp::IntEq => lhs == rhs,
_ => unreachable!()
}
}
TestOp::RegexMatch => {
// FIXME: Imagine doing all of this in every single iteration of a loop
let cleaned = replace_posix_classes(&rhs);
let regex = Regex::new(&cleaned).unwrap();
regex.is_match(&lhs)
}
}
}
};
flog!(DEBUG, last_result);
UnaryOp::Terminal => match operand.as_str().parse::<nix::libc::c_int>() {
Ok(fd) => unsafe { nix::libc::isatty(fd) == 1 },
Err(_) => false,
},
UnaryOp::NonNull => !operand.is_empty(),
UnaryOp::Null => operand.is_empty(),
}
}
TestCase::Binary {
lhs,
operator,
rhs,
conjunct,
} => {
let lhs = lhs.expand()?.get_words().join(" ");
let rhs = rhs.expand()?.get_words().join(" ");
conjunct_op = conjunct;
let test_op = operator.as_str().parse::<TestOp>()?;
flog!(DEBUG, lhs);
flog!(DEBUG, rhs);
flog!(DEBUG, test_op);
match test_op {
TestOp::Unary(_) => {
return Err(ShErr::Full {
kind: ShErrKind::SyntaxErr,
msg: "Expected a binary operator in this test call; found a unary operator".into(),
notes: vec![],
span: err_span,
})
}
TestOp::StringEq => rhs.trim() == lhs.trim(),
TestOp::StringNeq => rhs.trim() != lhs.trim(),
TestOp::IntNeq
| TestOp::IntGt
| TestOp::IntLt
| TestOp::IntGe
| TestOp::IntLe
| TestOp::IntEq => {
let err = ShErr::Full {
kind: ShErrKind::SyntaxErr,
msg: format!("Expected an integer with '{}' operator", operator.as_str()),
notes: vec![],
span: err_span.clone(),
};
let Ok(lhs) = lhs.trim().parse::<i32>() else {
return Err(err);
};
let Ok(rhs) = rhs.trim().parse::<i32>() else {
return Err(err);
};
match test_op {
TestOp::IntNeq => lhs != rhs,
TestOp::IntGt => lhs > rhs,
TestOp::IntLt => lhs < rhs,
TestOp::IntGe => lhs >= rhs,
TestOp::IntLe => lhs <= rhs,
TestOp::IntEq => lhs == rhs,
_ => unreachable!(),
}
}
TestOp::RegexMatch => {
// FIXME: Imagine doing all of this in every single iteration of a loop
let cleaned = replace_posix_classes(&rhs);
let regex = Regex::new(&cleaned).unwrap();
regex.is_match(&lhs)
}
}
}
};
flog!(DEBUG, last_result);
if let Some(op) = conjunct_op {
match op {
ConjunctOp::And if !last_result => {
last_result = result;
break
}
ConjunctOp::Or if last_result => {
last_result = result;
break
}
_ => {}
}
} else {
last_result = result;
}
}
flog!(DEBUG, last_result);
Ok(last_result)
if let Some(op) = conjunct_op {
match op {
ConjunctOp::And if !last_result => {
last_result = result;
break;
}
ConjunctOp::Or if last_result => {
last_result = result;
break;
}
_ => {}
}
} else {
last_result = result;
}
}
flog!(DEBUG, last_result);
Ok(last_result)
}

View File

@@ -1,190 +1,191 @@
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
use crate::{getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
use crate::{
getopt::{get_opts_from_tokens, Opt, OptSet},
jobs::JobBldr,
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
};
use super::setup_builtin;
pub static ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
[
Opt::Long("dry-run".into()),
Opt::Long("confirm".into()),
Opt::Long("no-preserve-root".into()),
Opt::Short('r'),
Opt::Short('f'),
Opt::Short('v')
].into()
[
Opt::Long("dry-run".into()),
Opt::Long("confirm".into()),
Opt::Long("no-preserve-root".into()),
Opt::Short('r'),
Opt::Short('f'),
Opt::Short('v'),
]
.into()
});
bitflags! {
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
struct ZoltFlags: u32 {
const DRY = 0b000001;
const CONFIRM = 0b000010;
const NO_PRESERVE_ROOT = 0b000100;
const RECURSIVE = 0b001000;
const FORCE = 0b010000;
const VERBOSE = 0b100000;
}
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
struct ZoltFlags: u32 {
const DRY = 0b000001;
const CONFIRM = 0b000010;
const NO_PRESERVE_ROOT = 0b000100;
const RECURSIVE = 0b001000;
const FORCE = 0b010000;
const VERBOSE = 0b100000;
}
}
/// Annihilate a file
///
/// This command works similarly to 'rm', but behaves more destructively.
/// 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.
/// 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<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
let mut flags = ZoltFlags::empty();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let mut flags = ZoltFlags::empty();
let (argv,opts) = get_opts_from_tokens(argv);
let (argv, opts) = get_opts_from_tokens(argv);
for opt in opts {
if !ZOLTRAAK_OPTS.contains(&opt) {
return Err(
ShErr::simple(
ShErrKind::SyntaxErr,
format!("zoltraak: unrecognized option '{opt}'")
)
)
}
match opt {
Opt::Long(flag) => {
match flag.as_str() {
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
"confirm" => flags |= ZoltFlags::CONFIRM,
"dry-run" => flags |= ZoltFlags::DRY,
_ => unreachable!()
}
}
Opt::Short(flag) => {
match flag {
'r' => flags |= ZoltFlags::RECURSIVE,
'f' => flags |= ZoltFlags::FORCE,
'v' => flags |= ZoltFlags::VERBOSE,
_ => unreachable!()
}
}
}
}
for opt in opts {
if !ZOLTRAAK_OPTS.contains(&opt) {
return Err(ShErr::simple(
ShErrKind::SyntaxErr,
format!("zoltraak: unrecognized option '{opt}'"),
));
}
match opt {
Opt::Long(flag) => match flag.as_str() {
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
"confirm" => flags |= ZoltFlags::CONFIRM,
"dry-run" => flags |= ZoltFlags::DRY,
_ => unreachable!(),
},
Opt::Short(flag) => match flag {
'r' => flags |= ZoltFlags::RECURSIVE,
'f' => flags |= ZoltFlags::FORCE,
'v' => flags |= ZoltFlags::VERBOSE,
_ => unreachable!(),
},
}
}
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut io_frame = io_frame.unwrap();
io_frame.redirect()?;
let mut io_frame = io_frame.unwrap();
io_frame.redirect()?;
for (arg,span) in argv {
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
"zoltraak: Attempted to destroy root directory '/'"
)
.with_note(
Note::new("If you really want to do this, you can use the --no-preserve-root flag")
.with_sub_notes(vec![
"Example: 'zoltraak --no-preserve-root /'"
])
)
)
}
if let Err(e) = annihilate(&arg, flags).blame(span) {
io_frame.restore()?;
return Err(e);
}
}
for (arg, span) in argv {
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
"zoltraak: Attempted to destroy root directory '/'",
)
.with_note(
Note::new("If you really want to do this, you can use the --no-preserve-root flag")
.with_sub_notes(vec!["Example: 'zoltraak --no-preserve-root /'"]),
),
);
}
if let Err(e) = annihilate(&arg, flags).blame(span) {
io_frame.restore()?;
return Err(e);
}
}
io_frame.restore()?;
io_frame.restore()?;
Ok(())
Ok(())
}
fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
let path_buf = PathBuf::from(path);
let is_recursive = flags.contains(ZoltFlags::RECURSIVE);
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
let path_buf = PathBuf::from(path);
let is_recursive = flags.contains(ZoltFlags::RECURSIVE);
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
const BLOCK_SIZE: u64 = 4096;
const BLOCK_SIZE: u64 = 4096;
if !path_buf.exists() {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
format!("zoltraak: File '{path}' not found")
)
)
}
if !path_buf.exists() {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("zoltraak: File '{path}' not found"),
));
}
if path_buf.is_file() {
let mut file = OpenOptions::new()
.write(true)
.custom_flags(libc::O_DIRECT)
.open(path_buf)?;
if path_buf.is_file() {
let mut file = OpenOptions::new()
.write(true)
.custom_flags(libc::O_DIRECT)
.open(path_buf)?;
let meta = file.metadata()?;
let file_size = meta.len();
let full_blocks = file_size / BLOCK_SIZE;
let byte_remainder = file_size % BLOCK_SIZE;
let meta = file.metadata()?;
let file_size = meta.len();
let full_blocks = file_size / BLOCK_SIZE;
let byte_remainder = file_size % BLOCK_SIZE;
let full_buf = vec![0; BLOCK_SIZE as usize];
let remainder_buf = vec![0; byte_remainder as usize];
let full_buf = vec![0; BLOCK_SIZE as usize];
let remainder_buf = vec![0; byte_remainder as usize];
for _ in 0..full_blocks {
file.write_all(&full_buf)?;
}
for _ in 0..full_blocks {
file.write_all(&full_buf)?;
}
if byte_remainder > 0 {
file.write_all(&remainder_buf)?;
}
if byte_remainder > 0 {
file.write_all(&remainder_buf)?;
}
file.set_len(0)?;
mem::drop(file);
fs::remove_file(path)?;
if is_verbose {
let stderr = borrow_fd(STDERR_FILENO);
write(stderr, format!("shredded file '{path}'\n").as_bytes())?;
}
file.set_len(0)?;
mem::drop(file);
fs::remove_file(path)?;
if is_verbose {
let stderr = borrow_fd(STDERR_FILENO);
write(stderr, format!("shredded file '{path}'\n").as_bytes())?;
}
} else if path_buf.is_dir() {
if is_recursive {
annihilate_recursive(path, flags)?; // scary
} else {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
format!("zoltraak: '{path}' is a directory"),
)
.with_note(
Note::new("Use the '-r' flag to recursively shred directories")
.with_sub_notes(vec!["Example: 'zoltraak -r directory'"]),
),
);
}
}
} else if path_buf.is_dir() {
if is_recursive {
annihilate_recursive(path, flags)?; // scary
} else {
return Err(
ShErr::simple(
ShErrKind::ExecFail,
format!("zoltraak: '{path}' is a directory")
)
.with_note(
Note::new("Use the '-r' flag to recursively shred directories")
.with_sub_notes(vec![
"Example: 'zoltraak -r directory'"
])
)
)
}
}
Ok(())
Ok(())
}
fn annihilate_recursive(dir: &str, flags: ZoltFlags) -> ShResult<()> {
let dir_path = PathBuf::from(dir);
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
let dir_path = PathBuf::from(dir);
let is_verbose = flags.contains(ZoltFlags::VERBOSE);
for dir_entry in fs::read_dir(&dir_path)? {
let entry = dir_entry?.path();
let file = entry.to_str().unwrap();
for dir_entry in fs::read_dir(&dir_path)? {
let entry = dir_entry?.path();
let file = entry.to_str().unwrap();
if entry.is_file() {
annihilate(file, flags)?;
} else if entry.is_dir() {
annihilate_recursive(file, flags)?;
}
}
fs::remove_dir(dir)?;
if is_verbose {
let stderr = borrow_fd(STDERR_FILENO);
write(stderr, format!("shredded directory '{dir}'\n").as_bytes())?;
}
Ok(())
if entry.is_file() {
annihilate(file, flags)?;
} else if entry.is_dir() {
annihilate_recursive(file, flags)?;
}
}
fs::remove_dir(dir)?;
if is_verbose {
let stderr = borrow_fd(STDERR_FILENO);
write(stderr, format!("shredded directory '{dir}'\n").as_bytes())?;
}
Ok(())
}