2017-06-14 03:56:20 +08:00
|
|
|
// Implementation of the function builtin.
|
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2021-11-10 09:07:15 +08:00
|
|
|
#include "function.h"
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2017-06-15 03:26:05 +08:00
|
|
|
#include <unistd.h>
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2019-11-19 09:11:16 +08:00
|
|
|
#include <cerrno>
|
|
|
|
#include <cstddef>
|
|
|
|
#include <cstdlib>
|
2017-06-15 03:26:05 +08:00
|
|
|
#include <memory>
|
2017-06-18 13:36:56 +08:00
|
|
|
#include <string>
|
2017-06-15 03:26:05 +08:00
|
|
|
#include <vector>
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2021-11-10 09:07:15 +08:00
|
|
|
#include "../builtin.h"
|
|
|
|
#include "../common.h"
|
|
|
|
#include "../complete.h"
|
|
|
|
#include "../event.h"
|
|
|
|
#include "../fallback.h" // IWYU pragma: keep
|
|
|
|
#include "../function.h"
|
|
|
|
#include "../io.h"
|
|
|
|
#include "../parser.h"
|
|
|
|
#include "../parser_keywords.h"
|
|
|
|
#include "../proc.h"
|
|
|
|
#include "../signal.h"
|
|
|
|
#include "../wgetopt.h"
|
|
|
|
#include "../wutil.h" // IWYU pragma: keep
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2021-09-28 08:34:49 +08:00
|
|
|
namespace {
|
2017-06-16 08:57:37 +08:00
|
|
|
struct function_cmd_opts_t {
|
2017-06-14 03:56:20 +08:00
|
|
|
bool print_help = false;
|
|
|
|
bool shadow_scope = true;
|
2020-04-03 10:43:34 +08:00
|
|
|
wcstring description;
|
2019-02-23 17:04:05 +08:00
|
|
|
std::vector<event_description_t> events;
|
2017-06-14 03:56:20 +08:00
|
|
|
wcstring_list_t named_arguments;
|
|
|
|
wcstring_list_t inherit_vars;
|
|
|
|
wcstring_list_t wrap_targets;
|
|
|
|
};
|
2021-09-28 08:34:49 +08:00
|
|
|
} // namespace
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2019-10-11 00:09:26 +08:00
|
|
|
// This command is atypical in using the "-" (RETURN_IN_ORDER) option for flag parsing.
|
2017-06-14 03:56:20 +08:00
|
|
|
// This is needed due to the semantics of the -a/--argument-names flag.
|
2019-10-11 00:09:26 +08:00
|
|
|
static const wchar_t *const short_options = L"-:a:d:e:hj:p:s:v:w:SV:";
|
2019-11-19 10:34:50 +08:00
|
|
|
static const struct woption long_options[] = {
|
|
|
|
{L"description", required_argument, nullptr, 'd'},
|
|
|
|
{L"on-signal", required_argument, nullptr, 's'},
|
|
|
|
{L"on-job-exit", required_argument, nullptr, 'j'},
|
|
|
|
{L"on-process-exit", required_argument, nullptr, 'p'},
|
|
|
|
{L"on-variable", required_argument, nullptr, 'v'},
|
|
|
|
{L"on-event", required_argument, nullptr, 'e'},
|
|
|
|
{L"wraps", required_argument, nullptr, 'w'},
|
|
|
|
{L"help", no_argument, nullptr, 'h'},
|
|
|
|
{L"argument-names", required_argument, nullptr, 'a'},
|
|
|
|
{L"no-scope-shadowing", no_argument, nullptr, 'S'},
|
|
|
|
{L"inherit-variable", required_argument, nullptr, 'V'},
|
2022-04-02 05:25:02 +08:00
|
|
|
{}};
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2021-05-21 02:25:32 +08:00
|
|
|
/// \return the internal_job_id for a pid, or 0 if none.
|
|
|
|
/// This looks through both active and finished jobs.
|
|
|
|
static internal_job_id_t job_id_for_pid(pid_t pid, parser_t &parser) {
|
|
|
|
if (const auto *job = parser.job_get_from_pid(pid)) {
|
|
|
|
return job->internal_job_id;
|
|
|
|
}
|
|
|
|
if (wait_handle_ref_t wh = parser.get_wait_handles().get_by_pid(pid)) {
|
|
|
|
return wh->internal_job_id;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-16 08:57:37 +08:00
|
|
|
static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method)
|
2021-02-14 10:41:09 +08:00
|
|
|
int argc, const wchar_t **argv, parser_t &parser, io_streams_t &streams) {
|
2017-06-18 13:36:56 +08:00
|
|
|
const wchar_t *cmd = L"function";
|
2017-06-14 03:56:20 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
2019-10-11 00:09:26 +08:00
|
|
|
bool handling_named_arguments = false;
|
2019-11-19 10:34:50 +08:00
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
2019-10-11 00:09:26 +08:00
|
|
|
if (opt != 'a' && opt != 1) handling_named_arguments = false;
|
2017-06-14 03:56:20 +08:00
|
|
|
switch (opt) {
|
2019-10-11 00:09:26 +08:00
|
|
|
case 1: {
|
|
|
|
if (handling_named_arguments) {
|
|
|
|
opts.named_arguments.push_back(w.woptarg);
|
|
|
|
break;
|
|
|
|
} else {
|
2021-11-02 18:09:38 +08:00
|
|
|
streams.err.append_format(_(L"%ls: %ls: unexpected positional argument"), cmd,
|
2019-10-11 00:09:26 +08:00
|
|
|
w.woptarg);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
}
|
2017-06-14 03:56:20 +08:00
|
|
|
case 'd': {
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.description = w.woptarg;
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 's': {
|
|
|
|
int sig = wcs2sig(w.woptarg);
|
|
|
|
if (sig == -1) {
|
2017-06-18 13:36:56 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2019-02-23 17:04:05 +08:00
|
|
|
opts.events.push_back(event_description_t::signal(sig));
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'v': {
|
|
|
|
if (!valid_var_name(w.woptarg)) {
|
2017-06-18 13:36:56 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
2019-02-23 17:04:05 +08:00
|
|
|
opts.events.push_back(event_description_t::variable(w.woptarg));
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'e': {
|
2019-02-23 17:04:05 +08:00
|
|
|
opts.events.push_back(event_description_t::generic(w.woptarg));
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'j':
|
|
|
|
case 'p': {
|
2019-02-23 17:04:05 +08:00
|
|
|
event_description_t e(event_type_t::any);
|
2017-06-14 03:56:20 +08:00
|
|
|
|
|
|
|
if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) {
|
2020-02-09 07:43:21 +08:00
|
|
|
internal_job_id_t caller_id =
|
|
|
|
parser.libdata().is_subshell ? parser.libdata().caller_id : 0;
|
|
|
|
if (caller_id == 0) {
|
2017-06-24 14:19:09 +08:00
|
|
|
streams.err.append_format(
|
2021-11-02 18:09:38 +08:00
|
|
|
_(L"%ls: calling job for event handler not found"), cmd);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2020-02-09 08:08:26 +08:00
|
|
|
e.type = event_type_t::caller_exit;
|
2020-02-09 07:43:21 +08:00
|
|
|
e.param1.caller_id = caller_id;
|
2018-04-29 18:31:47 +08:00
|
|
|
} else if ((opt == 'p') && (wcscasecmp(w.woptarg, L"%self") == 0)) {
|
2021-05-20 02:29:03 +08:00
|
|
|
e.type = event_type_t::process_exit;
|
2020-02-09 08:08:26 +08:00
|
|
|
e.param1.pid = getpid();
|
2017-06-14 03:56:20 +08:00
|
|
|
} else {
|
2020-02-09 08:08:26 +08:00
|
|
|
pid_t pid = fish_wcstoi(w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
if (errno || pid < 0) {
|
2021-11-02 18:09:38 +08:00
|
|
|
streams.err.append_format(_(L"%ls: %ls: invalid process id"), cmd,
|
2017-06-24 14:19:09 +08:00
|
|
|
w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2021-05-20 02:29:03 +08:00
|
|
|
if (opt == 'p') {
|
|
|
|
e.type = event_type_t::process_exit;
|
|
|
|
e.param1.pid = pid;
|
|
|
|
} else {
|
|
|
|
e.type = event_type_t::job_exit;
|
2021-05-21 02:25:32 +08:00
|
|
|
e.param1.jobspec = {pid, job_id_for_pid(pid, parser)};
|
2021-05-20 02:29:03 +08:00
|
|
|
}
|
2017-06-14 03:56:20 +08:00
|
|
|
}
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.events.push_back(e);
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'a': {
|
2019-10-11 00:09:26 +08:00
|
|
|
handling_named_arguments = true;
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.named_arguments.push_back(w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'S': {
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.shadow_scope = false;
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'w': {
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.wrap_targets.push_back(w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'V': {
|
|
|
|
if (!valid_var_name(w.woptarg)) {
|
2017-06-18 13:36:56 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.inherit_vars.push_back(w.woptarg);
|
2017-06-14 03:56:20 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'h': {
|
2017-06-16 08:57:37 +08:00
|
|
|
opts.print_help = true;
|
2017-06-15 13:12:29 +08:00
|
|
|
break;
|
2017-06-14 03:56:20 +08:00
|
|
|
}
|
|
|
|
case ':': {
|
2017-07-02 05:03:47 +08:00
|
|
|
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
DIE("unexpected retval from wgetopt_long");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*optind = w.woptind;
|
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int validate_function_name(int argc, const wchar_t *const *argv, wcstring &function_name,
|
2017-06-18 13:36:56 +08:00
|
|
|
const wchar_t *cmd, io_streams_t &streams) {
|
2017-06-14 03:56:20 +08:00
|
|
|
if (argc < 2) {
|
|
|
|
// This is currently impossible but let's be paranoid.
|
2021-11-02 18:09:38 +08:00
|
|
|
streams.err.append_format(_(L"%ls: function name required"), cmd);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
function_name = argv[1];
|
|
|
|
if (!valid_func_name(function_name)) {
|
2021-11-02 18:09:38 +08:00
|
|
|
streams.err.append_format(_(L"%ls: %ls: invalid function name"), cmd,
|
2017-06-24 14:19:09 +08:00
|
|
|
function_name.c_str());
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parser_keywords_is_reserved(function_name)) {
|
2021-11-09 03:36:01 +08:00
|
|
|
streams.err.append_format(_(L"%ls: %ls: cannot use reserved keyword as function name"), cmd,
|
|
|
|
function_name.c_str());
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Define a function. Calls into `function.cpp` to perform the heavy lifting of defining a
|
|
|
|
/// function.
|
2020-11-22 21:39:48 +08:00
|
|
|
maybe_t<int> builtin_function(parser_t &parser, io_streams_t &streams,
|
|
|
|
const wcstring_list_t &c_args, const parsed_source_ref_t &source,
|
|
|
|
const ast::block_statement_t &func_node) {
|
2018-02-10 13:53:06 +08:00
|
|
|
assert(source && "Missing source in builtin_function");
|
2017-06-14 03:56:20 +08:00
|
|
|
// The wgetopt function expects 'function' as the first argument. Make a new wcstring_list with
|
|
|
|
// that property. This is needed because this builtin has a different signature than the other
|
|
|
|
// builtins.
|
2017-06-18 13:36:56 +08:00
|
|
|
wcstring_list_t args = {L"function"};
|
2017-06-14 03:56:20 +08:00
|
|
|
args.insert(args.end(), c_args.begin(), c_args.end());
|
|
|
|
|
2018-08-05 07:15:06 +08:00
|
|
|
null_terminated_array_t<wchar_t> argv_array(args);
|
2021-02-14 10:41:09 +08:00
|
|
|
const wchar_t **argv = argv_array.get();
|
|
|
|
const wchar_t *cmd = argv[0];
|
2017-06-14 03:56:20 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
|
|
|
|
// A valid function name has to be the first argument.
|
2017-06-18 13:36:56 +08:00
|
|
|
wcstring function_name;
|
|
|
|
int retval = validate_function_name(argc, argv, function_name, cmd, streams);
|
2017-06-14 03:56:20 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
|
|
|
argv++;
|
|
|
|
argc--;
|
|
|
|
|
2017-06-18 13:36:56 +08:00
|
|
|
function_cmd_opts_t opts;
|
2017-06-14 03:56:20 +08:00
|
|
|
int optind;
|
2017-06-18 13:36:56 +08:00
|
|
|
retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
2017-06-14 03:56:20 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
|
|
|
|
|
|
|
if (opts.print_help) {
|
2019-03-27 02:13:01 +08:00
|
|
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc != optind) {
|
2019-11-19 08:56:46 +08:00
|
|
|
if (!opts.named_arguments.empty()) {
|
2017-06-14 03:56:20 +08:00
|
|
|
for (int i = optind; i < argc; i++) {
|
2019-10-08 03:21:38 +08:00
|
|
|
if (!valid_var_name(argv[i])) {
|
|
|
|
streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, argv[i]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2017-06-14 03:56:20 +08:00
|
|
|
opts.named_arguments.push_back(argv[i]);
|
|
|
|
}
|
|
|
|
} else {
|
2021-11-02 18:09:38 +08:00
|
|
|
streams.err.append_format(_(L"%ls: %ls: unexpected positional argument"), cmd,
|
2017-06-24 14:19:09 +08:00
|
|
|
argv[optind]);
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have what we need to actually define the function.
|
2019-11-13 01:53:10 +08:00
|
|
|
auto props = std::make_shared<function_properties_t>();
|
|
|
|
props->shadow_scope = opts.shadow_scope;
|
|
|
|
props->named_arguments = std::move(opts.named_arguments);
|
2019-11-13 03:25:41 +08:00
|
|
|
props->parsed_source = source;
|
2020-07-04 02:16:51 +08:00
|
|
|
props->func_node = &func_node;
|
2021-10-22 04:56:32 +08:00
|
|
|
props->description = opts.description;
|
2021-10-22 04:10:09 +08:00
|
|
|
props->definition_file = parser.libdata().current_filename;
|
2019-11-13 01:53:10 +08:00
|
|
|
|
|
|
|
// Populate inherit_vars.
|
|
|
|
for (const wcstring &name : opts.inherit_vars) {
|
|
|
|
if (auto var = parser.vars().get(name)) {
|
|
|
|
props->inherit_vars[name] = var->as_list();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-13 03:25:41 +08:00
|
|
|
// Add the function itself.
|
2021-10-22 04:56:32 +08:00
|
|
|
function_add(function_name, props);
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2021-05-18 06:22:02 +08:00
|
|
|
// Handle wrap targets by creating the appropriate completions.
|
|
|
|
for (const wcstring &wt : opts.wrap_targets) {
|
|
|
|
complete_add_wrapper(function_name, wt);
|
|
|
|
}
|
|
|
|
|
2019-11-13 03:25:41 +08:00
|
|
|
// Add any event handlers.
|
|
|
|
for (const event_description_t &ed : opts.events) {
|
|
|
|
event_add_handler(std::make_shared<event_handler_t>(ed, function_name));
|
|
|
|
}
|
2017-06-14 03:56:20 +08:00
|
|
|
|
2021-05-18 06:22:02 +08:00
|
|
|
// If there is an --on-process-exit or --on-job-exit event handler for some pid, and that
|
|
|
|
// process has already exited, run it immediately (#7210).
|
|
|
|
for (const event_description_t &ed : opts.events) {
|
2021-05-21 02:25:32 +08:00
|
|
|
if (ed.type == event_type_t::process_exit) {
|
|
|
|
pid_t pid = ed.param1.pid;
|
|
|
|
if (pid == EVENT_ANY_PID) continue;
|
|
|
|
wait_handle_ref_t wh = parser.get_wait_handles().get_by_pid(pid);
|
2021-05-18 06:22:02 +08:00
|
|
|
if (wh && wh->completed) {
|
2021-05-21 02:25:32 +08:00
|
|
|
event_fire(parser, event_t::process_exit(pid, wh->status));
|
|
|
|
}
|
|
|
|
} else if (ed.type == event_type_t::job_exit) {
|
|
|
|
pid_t pid = ed.param1.jobspec.pid;
|
|
|
|
if (pid == EVENT_ANY_PID) continue;
|
|
|
|
wait_handle_ref_t wh = parser.get_wait_handles().get_by_pid(pid);
|
|
|
|
if (wh && wh->completed) {
|
|
|
|
event_fire(parser, event_t::job_exit(pid, wh->internal_job_id));
|
2021-05-18 06:22:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-14 03:56:20 +08:00
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|