use super::prelude::*;
use crate::ast::BlockStatement;
use crate::common::{valid_func_name, valid_var_name};
use crate::complete::complete_add_wrapper;
use crate::env::environment::Environment as _;
use crate::env::is_read_only;
use crate::event::{self, EventDescription, EventHandler};
use crate::global_safety::RelaxedAtomicBool;
use crate::parse_execution::varname_error;
use crate::parse_tree::NodeRef;
use crate::parser_keywords::parser_keywords_is_reserved;
use crate::proc::Pid;
use crate::signal::Signal;
use crate::{err_fmt, err_str, function};
use nix::unistd::getpid;
use std::sync::Arc;
struct FunctionCmdOpts {
print_help: bool,
shadow_scope: bool,
description: WString,
events: Vec<EventDescription>,
named_arguments: Vec<WString>,
inherit_vars: Vec<WString>,
wrap_targets: Vec<WString>,
}
impl Default for FunctionCmdOpts {
fn default() -> Self {
Self {
print_help: false,
shadow_scope: true,
description: WString::new(),
events: Vec::new(),
named_arguments: Vec::new(),
inherit_vars: Vec::new(),
wrap_targets: Vec::new(),
}
}
}
const SHORT_OPTIONS: &wstr = L!("-a:d:e:hj:p:s:v:w:SV:");
#[rustfmt::skip]
const LONG_OPTIONS: &[WOption] = &[
wopt(L!("description"), ArgType::RequiredArgument, 'd'),
wopt(L!("on-signal"), ArgType::RequiredArgument, 's'),
wopt(L!("on-job-exit"), ArgType::RequiredArgument, 'j'),
wopt(L!("on-process-exit"), ArgType::RequiredArgument, 'p'),
wopt(L!("on-variable"), ArgType::RequiredArgument, 'v'),
wopt(L!("on-event"), ArgType::RequiredArgument, 'e'),
wopt(L!("wraps"), ArgType::RequiredArgument, 'w'),
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("argument-names"), ArgType::RequiredArgument, 'a'),
wopt(L!("no-scope-shadowing"), ArgType::NoArgument, 'S'),
wopt(L!("inherit-variable"), ArgType::RequiredArgument, 'V'),
];
fn job_id_for_pid(pid: Pid, parser: &Parser) -> Option<u64> {
if let Some(job) = parser.job_get_from_pid(pid) {
Some(job.internal_job_id)
} else {
parser
.get_wait_handles()
.get_by_pid(pid)
.map(|h| h.internal_job_id)
}
}
fn parse_cmd_opts(
opts: &mut FunctionCmdOpts,
argv: &mut [&wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> BuiltinResult {
let cmd = L!("function");
let print_hints = false;
let mut handling_named_arguments = false;
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
let mut validate_variable_name =
|streams: &mut IoStreams, varname: &wstr, read_only_ok: bool| {
if !valid_var_name(varname) {
varname_error(cmd, varname).finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if !read_only_ok && is_read_only(varname) {
err_fmt!("variable '%s' is read-only", varname)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(())
};
fn add_named_argument(
validate_variable_name: &mut impl FnMut(&mut IoStreams, &wstr, bool) -> Result<(), i32>,
streams: &mut IoStreams,
opts: &mut FunctionCmdOpts,
varname: &wstr,
) -> Result<(), i32> {
validate_variable_name(streams, varname, false)?;
opts.named_arguments.push(varname.to_owned());
Ok::<(), ErrorCode>(())
}
while let Some(opt) = w.next_opt() {
if opt != 'a' && opt != NON_OPTION_CHAR {
handling_named_arguments = false;
}
match opt {
NON_OPTION_CHAR => {
let woptarg = w.woptarg.unwrap();
if handling_named_arguments {
add_named_argument(&mut validate_variable_name, streams, opts, woptarg)?;
} else {
err_fmt!("%s: unexpected positional argument", woptarg)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
'd' => {
opts.description = w.woptarg.unwrap().to_owned();
}
's' => {
let Some(signal) = Signal::parse(w.woptarg.unwrap()) else {
err_fmt!("Unknown signal '%s'", w.woptarg.unwrap())
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
};
opts.events.push(EventDescription::Signal { signal });
}
'v' => {
let name = w.woptarg.unwrap().to_owned();
validate_variable_name(streams, &name, true)?;
opts.events.push(EventDescription::Variable { name });
}
'e' => {
let param = w.woptarg.unwrap().to_owned();
opts.events.push(EventDescription::Generic { param });
}
'j' | 'p' => {
let woptarg = w.woptarg.unwrap();
let e: EventDescription;
if opt == 'j' && woptarg == "caller" {
let caller_id = if parser.scope().is_subshell {
parser.scope().caller_id
} else {
0
};
if caller_id == 0 {
err_str!("calling job for event handler not found")
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
e = EventDescription::CallerExit { caller_id };
} else if opt == 'p' && woptarg == "%self" {
let pid = Pid::from_nix_pid_unchecked(getpid());
e = EventDescription::ProcessExit { pid: Some(pid) };
} else {
let pid = parse_pid_may_be_zero(streams, cmd, woptarg)?;
if opt == 'p' {
e = EventDescription::ProcessExit { pid };
} else {
let internal_job_id = pid
.and_then(|pid| job_id_for_pid(pid, parser))
.unwrap_or_default();
e = EventDescription::JobExit {
pid,
internal_job_id,
};
}
}
opts.events.push(e);
}
'a' => {
handling_named_arguments = true;
add_named_argument(
&mut validate_variable_name,
streams,
opts,
w.woptarg.unwrap(),
)?;
}
'S' => {
opts.shadow_scope = false;
}
'w' => {
opts.wrap_targets.push(w.woptarg.unwrap().to_owned());
}
'V' => {
let woptarg = w.woptarg.unwrap();
validate_variable_name(streams, woptarg, false)?;
opts.inherit_vars.push(woptarg.to_owned());
}
'h' => {
opts.print_help = true;
}
':' => {
builtin_missing_argument(
parser,
streams,
cmd,
None,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
';' => {
builtin_unexpected_argument(
parser,
streams,
cmd,
argv[w.wopt_index - 1],
print_hints,
);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.wopt_index - 1], print_hints);
return Err(STATUS_INVALID_ARGS);
}
other => {
panic!("Unexpected retval from WGetopter: {}", other);
}
}
}
let optind = w.wopt_index;
if argv.len() != optind {
if !opts.named_arguments.is_empty() {
for &arg in argv[optind..].iter() {
add_named_argument(&mut validate_variable_name, streams, opts, arg)?;
}
} else {
err_fmt!("%s: unexpected positional argument", argv[optind],)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
}
Ok(SUCCESS)
}
fn validate_function_name(
argv: &mut [&wstr],
function_name: &mut WString,
cmd: &wstr,
streams: &mut IoStreams,
) -> BuiltinResult {
if argv.len() < 2 {
streams
.err
.append(&wgettext_fmt!("%s: function name required", cmd));
return Err(STATUS_INVALID_ARGS);
}
*function_name = argv[1].to_owned();
if !valid_func_name(function_name) {
err_fmt!("%s: invalid function name", function_name,)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
if parser_keywords_is_reserved(function_name) {
err_fmt!(
"%s: cannot use reserved keyword as function name",
function_name
)
.cmd(cmd)
.finish(streams);
return Err(STATUS_INVALID_ARGS);
}
Ok(SUCCESS)
}
pub fn function(
parser: &Parser,
streams: &mut IoStreams,
c_args: &mut [&wstr],
func_node: NodeRef<BlockStatement>,
) -> BuiltinResult {
let mut args = vec![L!("function")];
args.extend_from_slice(c_args);
let argv: &mut [&wstr] = &mut args;
let cmd = argv[0];
let mut function_name = WString::new();
validate_function_name(argv, &mut function_name, cmd, streams)?;
let argv = &mut argv[1..];
let mut opts = FunctionCmdOpts::default();
parse_cmd_opts(&mut opts, argv, parser, streams)?;
if opts.print_help {
builtin_print_error_trailer(parser, streams.err, cmd);
return Ok(SUCCESS);
}
let definition_file = parser.libdata().current_filename.clone();
opts.inherit_vars.sort_unstable();
opts.inherit_vars.dedup();
let inherit_vars: Vec<(WString, Vec<WString>)> = opts
.inherit_vars
.into_iter()
.filter_map(|name| {
let vals = parser.vars().get(&name)?.as_list().to_vec();
Some((name, vals))
})
.collect();
let props = function::FunctionProperties {
func_node,
named_arguments: opts.named_arguments,
description: LocalizableString::from_external_source(opts.description),
inherit_vars: inherit_vars.into_boxed_slice(),
shadow_scope: opts.shadow_scope,
is_autoload: RelaxedAtomicBool::new(false),
definition_file,
is_copy: false,
copy_definition_file: None,
copy_definition_lineno: None,
};
function::add(function_name.clone(), Arc::new(props));
for wt in opts.wrap_targets {
complete_add_wrapper(function_name.clone(), wt.clone());
}
for ed in &opts.events {
event::add_handler(EventHandler::new(ed.clone(), Some(function_name.clone())));
}
for ed in &opts.events {
match *ed {
EventDescription::ProcessExit { pid: Some(pid) } => {
let wh = parser.get_wait_handles().get_by_pid(pid);
if let Some(status) = wh.and_then(|wh| wh.status()) {
event::fire(parser, event::Event::process_exit(pid, status));
}
}
EventDescription::JobExit { pid: Some(pid), .. } => {
let wh = parser.get_wait_handles().get_by_pid(pid);
if let Some(wh) = wh {
if wh.is_completed() {
event::fire(parser, event::Event::job_exit(pid, wh.internal_job_id));
}
}
}
_ => (),
}
}
Ok(SUCCESS)
}