Added rustfmt.toml, formatted codebase
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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, ""))
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user