// Functions for executing builtin functions. // // How to add a new builtin function: // // 1). Create a function in builtin.c with the following signature: // // static int builtin_NAME(parser_t &parser, io_streams_t &streams, wchar_t **argv) // // where NAME is the name of the builtin, and args is a zero-terminated list of arguments. // // 2). Add a line like { L"NAME", &builtin_NAME, N_(L"Bla bla bla") }, to the builtin_data_t // variable. The description is used by the completion system. Note that this array is sorted. // // 3). Create a file doc_src/NAME.txt, containing the manual for the builtin in Doxygen-format. // Check the other builtin manuals for proper syntax. // // 4). Use 'git add doc_src/NAME.txt' to start tracking changes to the documentation file. #include "config.h" // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "builtin_bind.h" #include "builtin_block.h" #include "builtin_commandline.h" #include "builtin_complete.h" #include "builtin_emit.h" #include "builtin_functions.h" #include "builtin_history.h" #include "builtin_jobs.h" #include "builtin_printf.h" #include "builtin_random.h" #include "builtin_read.h" #include "builtin_set.h" #include "builtin_set_color.h" #include "builtin_status.h" #include "builtin_string.h" #include "builtin_test.h" #include "builtin_ulimit.h" #include "common.h" #include "complete.h" #include "env.h" #include "event.h" #include "exec.h" #include "fallback.h" // IWYU pragma: keep #include "function.h" #include "intern.h" #include "io.h" #include "parse_constants.h" #include "parse_util.h" #include "parser.h" #include "parser_keywords.h" #include "path.h" #include "proc.h" #include "reader.h" #include "signal.h" #include "tokenizer.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep bool builtin_data_t::operator<(const wcstring &other) const { return wcscmp(this->name, other.c_str()) < 0; } bool builtin_data_t::operator<(const builtin_data_t *other) const { return wcscmp(this->name, other->name) < 0; } /// Counts the number of arguments in the specified null-terminated array int builtin_count_args(const wchar_t *const *argv) { int argc; for (argc = 1; argv[argc] != NULL;) { argc++; } assert(argv[argc] == NULL); return argc; } /// This function works like wperror, but it prints its result into the streams.err string instead /// to stderr. Used by the builtin commands. void builtin_wperror(const wchar_t *s, io_streams_t &streams) { char *err = strerror(errno); if (s != NULL) { streams.err.append(s); streams.err.append(L": "); } if (err != NULL) { const wcstring werr = str2wcstring(err); streams.err.append(werr); streams.err.push_back(L'\n'); } } /// Count the number of times the specified character occurs in the specified string. static int count_char(const wchar_t *str, wchar_t c) { int res = 0; for (; *str; str++) { res += (*str == c); } return res; } /// Obtain help/usage information for the specified builtin from manpage in subshell /// /// @param name /// builtin name to get up help for /// /// @return /// A wcstring with a formatted manpage. /// wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t *name) { UNUSED(parser); // This won't ever work if no_exec is set. if (no_exec) return wcstring(); wcstring_list_t lst; wcstring out; const wcstring name_esc = escape_string(name, 1); wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str()); if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { // since we're using a subshell, __fish_print_help can't tell we're in // a terminal. Tell it ourselves. int cols = common_get_width(); cmd = format_string(L"__fish_print_help --tty-width %d %ls", cols, name_esc.c_str()); } if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0) { for (size_t i = 0; i < lst.size(); i++) { out.append(lst.at(i)); out.push_back(L'\n'); } } return out; } /// Process and print for the specified builtin. If @c b is `sb_err`, also print the line /// information. /// /// If @c b is the buffer representing standard error, and the help message is about to be printed /// to an interactive screen, it may be shortened to fit the screen. /// void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, output_stream_t &b) { bool is_stderr = &b == &streams.err; if (is_stderr) { b.append(parser.current_line()); } const wcstring h = builtin_help_get(parser, streams, cmd); if (!h.size()) return; wchar_t *str = wcsdup(h.c_str()); if (str) { bool is_short = false; if (is_stderr) { // Interactive mode help to screen - only print synopsis if the rest won't fit. int screen_height, lines; screen_height = common_get_height(); lines = count_char(str, L'\n'); if (!shell_is_interactive() || (lines > 2 * screen_height / 3)) { wchar_t *pos; int cut = 0; int i; is_short = true; // First move down 4 lines. pos = str; for (i = 0; (i < 4) && pos && *pos; i++) { pos = wcschr(pos + 1, L'\n'); } if (pos && *pos) { // Then find the next empty line. for (; *pos; pos++) { if (*pos != L'\n') { continue; } int is_empty = 1; wchar_t *pos2; for (pos2 = pos + 1; *pos2; pos2++) { if (*pos2 == L'\n') break; if (*pos2 != L'\t' && *pos2 != L' ') { is_empty = 0; break; } } if (is_empty) { // And cut it. *(pos2 + 1) = L'\0'; cut = 1; break; } } } // We did not find a good place to cut message to shorten it - so we make sure we // don't print anything. if (!cut) { *str = 0; } } } b.append(str); if (is_short) { b.append_format(_(L"%ls: Type 'help %ls' for related documentation\n\n"), cmd, cmd); } free(str); } } /// Perform error reporting for encounter with unknown option. void builtin_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, const wchar_t *opt) { streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, opt); builtin_print_help(parser, streams, cmd, streams.err); } /// Perform error reporting for encounter with missing argument. void builtin_missing_argument(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, const wchar_t *opt) { streams.err.append_format(BUILTIN_ERR_MISSING, cmd, opt); builtin_print_help(parser, streams, cmd, streams.err); } /// The builtin builtin, used for giving builtins precedence over functions. Mostly handled by the /// parser. All this code does is some additional operational modes, such as printing a list of all /// builtins, printing help, etc. static int builtin_builtin(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); int list = 0; static const wchar_t *short_options = L"hn"; static const struct woption long_options[] = { {L"names", no_argument, 0, 'n'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case 'n': { list = 1; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (list) { wcstring_list_t names = builtin_get_names(); sort(names.begin(), names.end()); for (size_t i = 0; i < names.size(); i++) { const wchar_t *el = names.at(i).c_str(); streams.out.append(el); streams.out.append(L"\n"); } } return STATUS_CMD_OK; } /// Implementation of the builtin 'command'. Actual command running is handled by the parser, this /// just processes the flags. static int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); bool find_path = false; bool quiet = false; static const wchar_t *short_options = L"hqsv"; static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'}, {L"search", no_argument, NULL, 's'}, {L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case 's': case 'v': { find_path = true; break; } case 'q': { quiet = true; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (!find_path) { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_INVALID_ARGS; } int found = 0; for (int idx = w.woptind; argv[idx]; ++idx) { const wchar_t *command_name = argv[idx]; wcstring path; if (path_get_path(command_name, &path)) { if (!quiet) streams.out.append_format(L"%ls\n", path.c_str()); ++found; } } return found ? STATUS_CMD_OK : STATUS_CMD_ERROR; } /// A generic bultin that only supports showing a help message. This is only a placeholder that /// prints the help message. Useful for commands that live in the parser. static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); // Hackish - if we have no arguments other than the command, we are a "naked invocation" and we // just print help. if (argc == 1) { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_INVALID_ARGS; } static const wchar_t *short_options = L"h"; static const struct woption long_options[] = {{L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { //!OCLINT(too few branches) case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } return STATUS_CMD_ERROR; } // Convert a octal or hex character to its binary value. Surprisingly a version // of this function using a lookup table is only ~1.5% faster than the `switch` // statement version below. Since that requires initializing a table statically // (which is problematic if we run on an EBCDIC system) we don't use that // solution. Also, we relax the style rule that `case` blocks should always be // enclosed in parentheses given the nature of this code. static unsigned int builtin_echo_digit(wchar_t wc, unsigned int base) { assert(base == 8 || base == 16); // base must be hex or octal switch (wc) { case L'0': return 0; case L'1': return 1; case L'2': return 2; case L'3': return 3; case L'4': return 4; case L'5': return 5; case L'6': return 6; case L'7': return 7; default: { break; } } if (base != 16) return UINT_MAX; switch (wc) { case L'8': return 8; case L'9': return 9; case L'a': case L'A': return 10; case L'b': case L'B': return 11; case L'c': case L'C': return 12; case L'd': case L'D': return 13; case L'e': case L'E': return 14; case L'f': case L'F': return 15; default: { break; } } return UINT_MAX; } /// Parse a numeric escape sequence in str, returning whether we succeeded. Also return the number /// of characters consumed and the resulting value. Supported escape sequences: /// /// \0nnn: octal value, zero to three digits /// \nnn: octal value, one to three digits /// \xhh: hex value, one to two digits static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *consumed, unsigned char *out_val) { bool success = false; unsigned int start = 0; // the first character of the numeric part of the sequence unsigned int base = 0, max_digits = 0; if (builtin_echo_digit(str[0], 8) != UINT_MAX) { // Octal escape base = 8; // If the first digit is a 0, we allow four digits (including that zero); otherwise, we // allow 3. max_digits = (str[0] == L'0' ? 4 : 3); } else if (str[0] == L'x') { // Hex escape base = 16; max_digits = 2; // Skip the x start = 1; } if (base == 0) { return success; } unsigned int idx; unsigned char val = 0; // resulting character for (idx = start; idx < start + max_digits; idx++) { unsigned int digit = builtin_echo_digit(str[idx], base); if (digit == UINT_MAX) break; val = val * base + digit; } // We succeeded if we consumed at least one digit. if (idx > start) { *consumed = idx; *out_val = val; success = true; } return success; } /// The echo builtin. /// /// Bash only respects -n if it's the first argument. We'll do the same. We also support a new /// option -s to mean "no spaces" static int builtin_echo(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); // Skip first arg if (!*argv++) return STATUS_INVALID_ARGS; // Process options. Options must come at the beginning - the first non-option kicks us out. bool print_newline = true, print_spaces = true, interpret_special_chars = false; size_t option_idx = 0; for (option_idx = 0; argv[option_idx] != NULL; option_idx++) { const wchar_t *arg = argv[option_idx]; assert(arg != NULL); bool arg_is_valid_option = false; if (arg[0] == L'-') { // We have a leading dash. Ensure that every subseqnent character is a valid option. size_t i = 1; while (arg[i] != L'\0' && wcschr(L"nesE", arg[i]) != NULL) { i++; } // We must have at least two characters to be a valid option, and have consumed the // whole string. arg_is_valid_option = (i >= 2 && arg[i] == L'\0'); } if (!arg_is_valid_option) { // This argument is not an option, so there are no more options. break; } // Ok, we are sure the argument is an option. Parse it. assert(arg_is_valid_option); for (size_t i = 1; arg[i] != L'\0'; i++) { switch (arg[i]) { case L'n': { print_newline = false; break; } case L'e': { interpret_special_chars = true; break; } case L's': { print_spaces = false; break; } case L'E': { interpret_special_chars = false; break; } default: { DIE("unexpected character in builtin_echo argument"); break; } } } } // The special character \c can be used to indicate no more output. bool continue_output = true; /* Skip over the options */ const wchar_t *const *args_to_echo = argv + option_idx; for (size_t idx = 0; continue_output && args_to_echo[idx] != NULL; idx++) { if (print_spaces && idx > 0) { streams.out.push_back(' '); } const wchar_t *str = args_to_echo[idx]; for (size_t j = 0; continue_output && str[j]; j++) { if (!interpret_special_chars || str[j] != L'\\') { // Not an escape. streams.out.push_back(str[j]); } else { // Most escapes consume one character in addition to the backslash; the numeric // sequences may consume more, while an unrecognized escape sequence consumes none. wchar_t wc; size_t consumed = 1; switch (str[j + 1]) { case L'a': { wc = L'\a'; break; } case L'b': { wc = L'\b'; break; } case L'e': { wc = L'\e'; break; } case L'f': { wc = L'\f'; break; } case L'n': { wc = L'\n'; break; } case L'r': { wc = L'\r'; break; } case L't': { wc = L'\t'; break; } case L'v': { wc = L'\v'; break; } case L'\\': { wc = L'\\'; break; } case L'c': { wc = 0; continue_output = false; break; } default: { // Octal and hex escape sequences. unsigned char narrow_val = 0; if (builtin_echo_parse_numeric_sequence(str + j + 1, &consumed, &narrow_val)) { // Here consumed must have been set to something. The narrow_val is a // literal byte that we want to output (#1894). wc = ENCODE_DIRECT_BASE + narrow_val % 256; } else { // Not a recognized escape. We consume only the backslash. wc = L'\\'; consumed = 0; } break; } } // Skip over characters that were part of this escape sequence (but not the // backslash, which will be handled by the loop increment. j += consumed; if (continue_output) { streams.out.push_back(wc); } } } } if (print_newline && continue_output) { streams.out.push_back('\n'); } return STATUS_CMD_OK; } /// The pwd builtin. We don't respect -P to resolve symbolic links because we /// try to always resolve them. static int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv)); return STATUS_INVALID_ARGS; } wcstring res = wgetcwd(); if (res.empty()) { return STATUS_CMD_ERROR; } streams.out.append(res); streams.out.push_back(L'\n'); return STATUS_CMD_OK; } static int validate_function_name(int argc, const wchar_t *const *argv, wcstring &function_name, const wchar_t *cmd, wcstring *out_err) { if (argc < 2) { // This is currently impossible but let's be paranoid. append_format(*out_err, _(L"%ls: Expected function name"), cmd); return STATUS_INVALID_ARGS; } function_name = argv[1]; if (!valid_func_name(function_name)) { append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), cmd, function_name.c_str()); return STATUS_INVALID_ARGS; } if (parser_keywords_is_reserved(function_name)) { append_format( *out_err, _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"), cmd, function_name.c_str()); return STATUS_INVALID_ARGS; } return STATUS_CMD_OK; } /// Define a function. Calls into `function.cpp` to perform the heavy lifting of defining a /// function. int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_list_t &c_args, const wcstring &contents, int definition_line_offset, wcstring *out_err) { assert(out_err != NULL); // 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. wcstring_list_t args; args.push_back(L"function"); args.insert(args.end(), c_args.begin(), c_args.end()); // Hackish const_cast matches the one in builtin_run. const null_terminated_array_t argv_array(args); wchar_t **argv = const_cast(argv_array.get()); wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); wchar_t *desc = NULL; bool shadow_scope = true; wcstring function_name; std::vector events; wcstring_list_t named_arguments; wcstring_list_t inherit_vars; wcstring_list_t wrap_targets; // A valid function name has to be the first argument. if (validate_function_name(argc, argv, function_name, cmd, out_err) == STATUS_CMD_OK) { argv++; argc--; } else { return STATUS_INVALID_ARGS; } // This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing. // This is needed due to the semantics of the -a/--argument-names flag. static const wchar_t *short_options = L"+:a:d:e:hj:p:s:v:w:SV:"; static const struct woption long_options[] = { {L"description", required_argument, NULL, 'd'}, {L"on-signal", required_argument, NULL, 's'}, {L"on-job-exit", required_argument, NULL, 'j'}, {L"on-process-exit", required_argument, NULL, 'p'}, {L"on-variable", required_argument, NULL, 'v'}, {L"on-event", required_argument, NULL, 'e'}, {L"wraps", required_argument, NULL, 'w'}, {L"help", no_argument, NULL, 'h'}, {L"argument-names", required_argument, NULL, 'a'}, {L"no-scope-shadowing", no_argument, NULL, 'S'}, {L"inherit-variable", required_argument, NULL, 'V'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'd': { desc = w.woptarg; break; } case 's': { int sig = wcs2sig(w.woptarg); if (sig == -1) { append_format(*out_err, _(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } events.push_back(event_t::signal_event(sig)); break; } case 'v': { if (!valid_var_name(w.woptarg)) { append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); return STATUS_INVALID_ARGS; } events.push_back(event_t::variable_event(w.woptarg)); break; } case 'e': { events.push_back(event_t::generic_event(w.woptarg)); break; } case 'j': case 'p': { pid_t pid; event_t e(EVENT_ANY); if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { job_id_t job_id = -1; if (is_subshell) { size_t block_idx = 0; // Find the outermost substitution block. for (block_idx = 0;; block_idx++) { const block_t *b = parser.block_at_index(block_idx); if (b == NULL || b->type() == SUBST) break; } // Go one step beyond that, to get to the caller. const block_t *caller_block = parser.block_at_index(block_idx + 1); if (caller_block != NULL && caller_block->job != NULL) { job_id = caller_block->job->job_id; } } if (job_id == -1) { append_format(*out_err, _(L"%ls: Cannot find calling job for event handler"), cmd); return STATUS_INVALID_ARGS; } e.type = EVENT_JOB_ID; e.param1.job_id = job_id; } else { pid = fish_wcstoi(w.woptarg); if (errno || pid < 0) { append_format(*out_err, _(L"%ls: Invalid process id '%ls'"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } e.type = EVENT_EXIT; e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); } events.push_back(e); break; } case 'a': { named_arguments.push_back(w.woptarg); break; } case 'S': { shadow_scope = false; break; } case 'w': { wrap_targets.push_back(w.woptarg); break; } case 'V': { if (!valid_var_name(w.woptarg)) { append_format(*out_err, BUILTIN_ERR_VARNAME, cmd, w.woptarg); return STATUS_INVALID_ARGS; } inherit_vars.push_back(w.woptarg); break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case ':': { streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); 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"); break; } } } if (argc != w.woptind) { if (named_arguments.size()) { for (int i = w.woptind; i < argc; i++) { named_arguments.push_back(argv[i]); } } else { append_format(*out_err, _(L"%ls: Unexpected positional argument '%ls'"), cmd, argv[w.woptind]); return STATUS_INVALID_ARGS; } } // We have what we need to actually define the function. function_data_t d; d.name = function_name; if (desc) d.description = desc; d.events.swap(events); d.shadow_scope = shadow_scope; d.named_arguments.swap(named_arguments); d.inherit_vars.swap(inherit_vars); for (size_t i = 0; i < d.events.size(); i++) { event_t &e = d.events.at(i); e.function_name = d.name; } d.definition = contents.c_str(); function_add(d, parser, definition_line_offset); // Handle wrap targets by creating the appropriate completions. for (size_t w = 0; w < wrap_targets.size(); w++) { complete_add_wrapper(function_name, wrap_targets.at(w)); } return STATUS_CMD_OK; } /// The exit builtin. Calls reader_exit to exit and returns the value specified. static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); if (argc > 2) { streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } long ec; if (argc == 1) { ec = proc_get_last_status(); } else { ec = fish_wcstol(argv[1]); if (errno) { streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } } reader_exit(1, 0); return (int)ec; } /// The cd builtin. Changes the current directory to the one specified or to $HOME if none is /// specified. The directory can be relative to any directory in the CDPATH variable. /// The cd builtin. Changes the current directory to the one specified or to $HOME if none is /// specified. The directory can be relative to any directory in the CDPATH variable. static int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { env_var_t dir_in; wcstring dir; if (argv[1] == NULL) { dir_in = env_get_string(L"HOME"); if (dir_in.missing_or_empty()) { streams.err.append_format(_(L"%ls: Could not find home directory\n"), argv[0]); return STATUS_CMD_ERROR; } } else { dir_in = env_var_t(argv[1]); } bool got_cd_path = false; if (!dir_in.missing()) { got_cd_path = path_get_cdpath(dir_in, &dir); } if (!got_cd_path) { if (errno == ENOTDIR) { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), argv[0], dir_in.c_str()); } else if (errno == ENOENT) { streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), argv[0], dir_in.c_str()); } else if (errno == EROTTEN) { streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"), argv[0], dir_in.c_str()); } else { streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"), argv[0], dir_in.c_str()); } if (!shell_is_interactive()) streams.err.append(parser.current_line()); return STATUS_CMD_ERROR; } if (wchdir(dir) != 0) { struct stat buffer; int status; status = wstat(dir, &buffer); if (!status && S_ISDIR(buffer.st_mode)) { streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), argv[0], dir.c_str()); } else { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), argv[0], dir.c_str()); } if (!shell_is_interactive()) { streams.err.append(parser.current_line()); } return STATUS_CMD_ERROR; } if (!env_set_pwd()) { streams.err.append_format(_(L"%ls: Could not set PWD variable\n"), argv[0]); return STATUS_CMD_ERROR; } return STATUS_CMD_OK; } /// Implementation of the builtin count command, used to count the number of arguments sent to it. static int builtin_count(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); int argc; argc = builtin_count_args(argv); streams.out.append_format(L"%d\n", argc - 1); return !(argc - 1); } /// Implementation of the builtin contains command, used to check if a specified string is part of /// a list. static int builtin_contains(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); wchar_t *needle; bool should_output_index = false; static const wchar_t *short_options = L"+hi"; static const struct woption long_options[] = { {L"help", no_argument, NULL, 'h'}, {L"index", no_argument, NULL, 'i'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case 'i': { should_output_index = true; break; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } needle = argv[w.woptind]; if (!needle) { streams.err.append_format(_(L"%ls: Key not specified\n"), argv[0]); } else { for (int i = w.woptind + 1; i < argc; i++) { if (!wcscmp(needle, argv[i])) { if (should_output_index) streams.out.append_format(L"%d\n", i - w.woptind); return STATUS_CMD_OK; } } } return STATUS_CMD_ERROR; } /// The . (dot) builtin, sometimes called source. Evaluates the contents of a file. static int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); int fd; int res = STATUS_CMD_OK; struct stat buf; int argc; argc = builtin_count_args(argv); const wchar_t *fn, *fn_intern; if (argc < 2 || (wcscmp(argv[1], L"-") == 0)) { fn = L"-"; fn_intern = fn; fd = dup(streams.stdin_fd); } else { if ((fd = wopen_cloexec(argv[1], O_RDONLY)) == -1) { streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]); builtin_wperror(L"source", streams); return STATUS_CMD_ERROR; } if (fstat(fd, &buf) == -1) { close(fd); streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]); builtin_wperror(L"source", streams); return STATUS_CMD_ERROR; } if (!S_ISREG(buf.st_mode)) { close(fd); streams.err.append_format(_(L"%ls: '%ls' is not a file\n"), argv[0], argv[1]); return STATUS_CMD_ERROR; } fn_intern = intern(argv[1]); } const source_block_t *sb = parser.push_block(fn_intern); reader_push_current_filename(fn_intern); env_set_argv(argc > 1 ? argv + 2 : argv + 1); res = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(sb); if (res) { streams.err.append_format(_(L"%ls: Error while reading file '%ls'\n"), argv[0], fn_intern == intern_static(L"-") ? L"" : fn_intern); } else { res = proc_get_last_status(); } // Do not close fd after calling reader_read. reader_read automatically closes it before calling // eval. reader_pop_current_filename(); return res; } /// Builtin for putting a job in the foreground. static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { job_t *j = NULL; if (argv[1] == 0) { // Select last constructed job (I.e. first job in the job que) that is possible to put in // the foreground. job_iterator_t jobs; while ((j = jobs.next())) { if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j)) && ((job_is_stopped(j) || (!j->get_flag(JOB_FOREGROUND))) && j->get_flag(JOB_CONTROL))) { break; } } if (!j) { streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]); } } else if (argv[2] != 0) { // Specifying more than one job to put to the foreground is a syntax error, we still // try to locate the job argv[1], since we want to know if this is an ambigous job // specification or if this is an malformed job id. int pid; int found_job = 0; pid = fish_wcstoi(argv[1]); if (!(errno || pid < 0)) { j = job_get_from_pid(pid); if (j) found_job = 1; } if (found_job) { streams.err.append_format(_(L"%ls: Ambiguous job\n"), argv[0]); } else { streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[1]); } builtin_print_help(parser, streams, argv[0], streams.err); j = 0; } else { int pid = abs(fish_wcstoi(argv[1])); if (errno) { streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); } else { j = job_get_from_pid(pid); if (!j || !j->get_flag(JOB_CONSTRUCTED) || job_is_completed(j)) { streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid); j = 0; } else if (!j->get_flag(JOB_CONTROL)) { streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because " L"it is not under job control\n"), argv[0], pid, j->command_wcstr()); j = 0; } } } if (!j) { return STATUS_INVALID_ARGS; } if (streams.err_is_redirected) { streams.err.append_format(FG_MSG, j->job_id, j->command_wcstr()); } else { // If we aren't redirecting, send output to real stderr, since stuff in sb_err won't get // printed until the command finishes. fwprintf(stderr, FG_MSG, j->job_id, j->command_wcstr()); } const wcstring ft = tok_first(j->command()); if (!ft.empty()) env_set(L"_", ft.c_str(), ENV_EXPORT); reader_write_title(j->command()); job_promote(j); j->set_flag(JOB_FOREGROUND, true); job_continue(j, job_is_stopped(j)); return STATUS_CMD_OK; } /// Helper function for builtin_bg(). static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j) { assert(j != NULL); if (!j->get_flag(JOB_CONTROL)) { streams.err.append_format( _(L"%ls: Can't put job %d, '%ls' to background because it is not under job control\n"), L"bg", j->job_id, j->command_wcstr()); builtin_print_help(parser, streams, L"bg", streams.err); return STATUS_CMD_ERROR; } streams.err.append_format(_(L"Send job %d '%ls' to background\n"), j->job_id, j->command_wcstr()); job_promote(j); j->set_flag(JOB_FOREGROUND, false); job_continue(j, job_is_stopped(j)); return STATUS_CMD_OK; } /// Builtin for putting a job in the background. static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int res = STATUS_CMD_OK; if (!argv[1]) { // No jobs were specified so use the most recent (i.e., last) job. job_t *j; job_iterator_t jobs; while ((j = jobs.next())) { if (job_is_stopped(j) && j->get_flag(JOB_CONTROL) && (!job_is_completed(j))) { break; } } if (!j) { streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]); res = STATUS_CMD_ERROR; } else { res = send_to_bg(parser, streams, j); } return res; } // The user specified at least one job to be backgrounded. std::vector pids; // If one argument is not a valid pid (i.e. integer >= 0), fail without backgrounding anything, // but still print errors for all of them. for (int i = 1; argv[i]; i++) { int pid = fish_wcstoi(argv[i]); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), L"bg", argv[i]); res = STATUS_INVALID_ARGS; } pids.push_back(pid); } if (res != STATUS_CMD_OK) return res; // Background all existing jobs that match the pids. // Non-existent jobs aren't an error, but information about them is useful. for (auto p : pids) { if (job_t *j = job_get_from_pid(p)) { res |= send_to_bg(parser, streams, j); } else { streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), argv[0], p); } } return res; } /// Helper for builtin_disown static int disown_job(parser_t &parser, io_streams_t &streams, job_t *j) { if (j == 0) { streams.err.append_format(_(L"%ls: Unknown job '%ls'\n"), L"bg"); builtin_print_help(parser, streams, L"disown", streams.err); return STATUS_INVALID_ARGS; } // Stopped disowned jobs must be manually signalled; explain how to do so if (job_is_stopped(j)) { killpg(j->pgid, SIGCONT); const wchar_t *fmt = _(L"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n"); streams.err.append_format(fmt, L"disown", j->job_id, j->command_wcstr()); } if (parser.job_remove(j)) return STATUS_CMD_OK; return STATUS_CMD_ERROR; } /// Builtin for removing jobs from the job list static int builtin_disown(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int res = STATUS_CMD_OK; if (argv[1] == 0) { job_t *j; // Select last constructed job (ie first job in the job queue) that is possible to disown. // Stopped jobs can be disowned (they will be continued). // Foreground jobs can be disowned. // Even jobs that aren't under job control can be disowned! job_iterator_t jobs; while ((j = jobs.next())) { if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j))) { break; } } if (j) { res = disown_job(parser, streams, j); } else { streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]); res = STATUS_CMD_ERROR; } } else { std::set jobs; // If one argument is not a valid pid (i.e. integer >= 0), fail without disowning anything, // but still print errors for all of them. // Non-existent jobs aren't an error, but information about them is useful. // Multiple PIDs may refer to the same job; include the job only once by using a set. for (int i = 1; argv[i]; i++) { int pid = fish_wcstoi(argv[i]); if (errno || pid < 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), argv[0], argv[i]); res = STATUS_INVALID_ARGS; } else { if (job_t *j = parser.job_get_from_pid(pid)) { jobs.insert(j); } else { streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), argv[0], pid); } } } if (res != STATUS_CMD_OK) { return res; } // Disown all target jobs for (auto j : jobs) { res |= disown_job(parser, streams, j); } } return res; } /// This function handles both the 'continue' and the 'break' builtins that are used for loop /// control. static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int is_break = (wcscmp(argv[0], L"break") == 0); int argc = builtin_count_args(argv); if (argc != 1) { streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } // Find the index of the enclosing for or while loop. Recall that incrementing loop_idx goes // 'up' to outer blocks. size_t loop_idx; for (loop_idx = 0; loop_idx < parser.block_count(); loop_idx++) { const block_t *b = parser.block_at_index(loop_idx); if (b->type() == WHILE || b->type() == FOR) break; } if (loop_idx >= parser.block_count()) { streams.err.append_format(_(L"%ls: Not inside of loop\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_CMD_ERROR; } // Skip blocks interior to the loop (but not the loop itself) size_t block_idx = loop_idx; while (block_idx--) { parser.block_at_index(block_idx)->skip = true; } // Mark the loop's status block_t *loop_block = parser.block_at_index(loop_idx); loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE; return STATUS_CMD_OK; } /// Implementation of the builtin breakpoint command, used to launch the interactive debugger. static int builtin_breakpoint(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv)); return STATUS_INVALID_ARGS; } const breakpoint_block_t *bpb = parser.push_block(); reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(bpb); return proc_get_last_status(); } /// Function for handling the \c return builtin. static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); if (argc > 2) { streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } int status; if (argc == 2) { status = fish_wcstoi(argv[1]); if (errno) { streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } } else { status = proc_get_last_status(); } // Find the function block. size_t function_block_idx; for (function_block_idx = 0; function_block_idx < parser.block_count(); function_block_idx++) { const block_t *b = parser.block_at_index(function_block_idx); if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) break; } if (function_block_idx >= parser.block_count()) { streams.err.append_format(_(L"%ls: Not inside of function\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_CMD_ERROR; } // Skip everything up to and including the function block. for (size_t i = 0; i <= function_block_idx; i++) { block_t *b = parser.block_at_index(i); b->skip = true; } return status; } int builtin_true(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); UNUSED(streams); if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv) - 1); return STATUS_INVALID_ARGS; } return STATUS_CMD_OK; } int builtin_false(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); UNUSED(streams); if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv) - 1); return STATUS_INVALID_ARGS; } return STATUS_CMD_ERROR; } /// An implementation of the external realpath command that doesn't support any options. It's meant /// to be used only by scripts which need to be portable. In general scripts shouldn't invoke this /// directly. They should just use `realpath` which will fallback to this builtin if an external /// command cannot be found. This behaves like the external `realpath --canonicalize-existing`; /// that is, it requires all path components, including the final, to exist. int builtin_realpath(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int argc = builtin_count_args(argv); if (argc != 2) { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_INVALID_ARGS; } wchar_t *real_path = wrealpath(argv[1], NULL); if (real_path) { streams.out.append(real_path); free((void *)real_path); } else { // We don't actually know why it failed. We should check errno. streams.err.append_format(_(L"%ls: Invalid path: %ls\n"), argv[0], argv[1]); return STATUS_CMD_ERROR; } streams.out.append(L"\n"); return STATUS_CMD_OK; } // END OF BUILTIN COMMANDS // Below are functions for handling the builtin commands. // THESE MUST BE SORTED BY NAME! Completion lookup uses binary search. // Data about all the builtin commands in fish. // Functions that are bound to builtin_generic are handled directly by the parser. // NOTE: These must be kept in sorted order! static const builtin_data_t builtin_datas[] = { {L"[", &builtin_test, N_(L"Test a condition")}, {L"and", &builtin_generic, N_(L"Execute command if previous command suceeded")}, {L"begin", &builtin_generic, N_(L"Create a block of code")}, {L"bg", &builtin_bg, N_(L"Send job to background")}, {L"bind", &builtin_bind, N_(L"Handle fish key bindings")}, {L"block", &builtin_block, N_(L"Temporarily block delivery of events")}, {L"break", &builtin_break_continue, N_(L"Stop the innermost loop")}, {L"breakpoint", &builtin_breakpoint, N_(L"Temporarily halt execution of a script and launch an interactive debug prompt")}, {L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function")}, {L"case", &builtin_generic, N_(L"Conditionally execute a block of commands")}, {L"cd", &builtin_cd, N_(L"Change working directory")}, {L"command", &builtin_command, N_(L"Run a program instead of a function or builtin")}, {L"commandline", &builtin_commandline, N_(L"Set or get the commandline")}, {L"complete", &builtin_complete, N_(L"Edit command specific completions")}, {L"contains", &builtin_contains, N_(L"Search for a specified string in a list")}, {L"continue", &builtin_break_continue, N_(L"Skip the rest of the current lap of the innermost loop")}, {L"count", &builtin_count, N_(L"Count the number of arguments")}, {L"disown", &builtin_disown, N_(L"Remove job from job list")}, {L"echo", &builtin_echo, N_(L"Print arguments")}, {L"else", &builtin_generic, N_(L"Evaluate block if condition is false")}, {L"emit", &builtin_emit, N_(L"Emit an event")}, {L"end", &builtin_generic, N_(L"End a block of commands")}, {L"exec", &builtin_generic, N_(L"Run command in current process")}, {L"exit", &builtin_exit, N_(L"Exit the shell")}, {L"false", &builtin_false, N_(L"Return an unsuccessful result")}, {L"fg", &builtin_fg, N_(L"Send job to foreground")}, {L"for", &builtin_generic, N_(L"Perform a set of commands multiple times")}, {L"function", &builtin_generic, N_(L"Define a new function")}, {L"functions", &builtin_functions, N_(L"List or remove functions")}, {L"history", &builtin_history, N_(L"History of commands executed by user")}, {L"if", &builtin_generic, N_(L"Evaluate block if condition is true")}, {L"jobs", &builtin_jobs, N_(L"Print currently running jobs")}, {L"not", &builtin_generic, N_(L"Negate exit status of job")}, {L"or", &builtin_generic, N_(L"Execute command if previous command failed")}, {L"printf", &builtin_printf, N_(L"Prints formatted text")}, {L"pwd", &builtin_pwd, N_(L"Print the working directory")}, {L"random", &builtin_random, N_(L"Generate random number")}, {L"read", &builtin_read, N_(L"Read a line of input into variables")}, {L"realpath", &builtin_realpath, N_(L"Convert path to absolute path without symlinks")}, {L"return", &builtin_return, N_(L"Stop the currently evaluated function")}, {L"set", &builtin_set, N_(L"Handle environment variables")}, {L"set_color", &builtin_set_color, N_(L"Set the terminal color")}, {L"source", &builtin_source, N_(L"Evaluate contents of file")}, {L"status", &builtin_status, N_(L"Return status information about fish")}, {L"string", &builtin_string, N_(L"Manipulate strings")}, {L"switch", &builtin_generic, N_(L"Conditionally execute a block of commands")}, {L"test", &builtin_test, N_(L"Test a condition")}, {L"true", &builtin_true, N_(L"Return a successful result")}, {L"ulimit", &builtin_ulimit, N_(L"Set or get the shells resource usage limits")}, {L"while", &builtin_generic, N_(L"Perform a command multiple times")}}; #define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas) /// Look up a builtin_data_t for a specified builtin /// /// @param name /// Name of the builtin /// /// @return /// Pointer to a builtin_data_t /// static const builtin_data_t *builtin_lookup(const wcstring &name) { const builtin_data_t *array_end = builtin_datas + BUILTIN_COUNT; const builtin_data_t *found = std::lower_bound(builtin_datas, array_end, name); if (found != array_end && name == found->name) { return found; } return NULL; } /// Initialize builtin data. void builtin_init() { for (size_t i = 0; i < BUILTIN_COUNT; i++) { intern_static(builtin_datas[i].name); } } /// Destroy builtin data. void builtin_destroy() {} /// Is there a builtin command with the given name? bool builtin_exists(const wcstring &cmd) { return static_cast(builtin_lookup(cmd)); } /// If builtin takes care of printing help itself static const wcstring_list_t help_builtins({L"for", L"while", L"function", L"if", L"end", L"switch", L"case", L"count", L"printf"}); static bool builtin_handles_help(const wchar_t *cmd) { CHECK(cmd, 0); return contains(help_builtins, cmd); } /// Execute a builtin command int builtin_run(parser_t &parser, const wchar_t *const *argv, io_streams_t &streams) { UNUSED(parser); UNUSED(streams); if (argv == NULL || argv[0] == NULL) return STATUS_INVALID_ARGS; const builtin_data_t *data = builtin_lookup(argv[0]); if (argv[1] != NULL && !builtin_handles_help(argv[0]) && argv[2] == NULL && parse_util_argument_is_help(argv[1], 0)) { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } if (data != NULL) { // Warning: layering violation and naughty cast. The code originally had a much more // complicated solution to achieve exactly the same result: lie about the constness of argv. // Some of the builtins we call do mutate the array via their calls to wgetopt() which could // result in the pointers being reordered. This is harmless because we only get called once // with a given argv array and nothing else will look at the contents of the array after we // return. return data->func(parser, streams, (wchar_t **)argv); } debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]); return STATUS_CMD_ERROR; } /// Returns a list of all builtin names. wcstring_list_t builtin_get_names(void) { wcstring_list_t result; result.reserve(BUILTIN_COUNT); for (size_t i = 0; i < BUILTIN_COUNT; i++) { result.push_back(builtin_datas[i].name); } return result; } /// Insert all builtin names into list. void builtin_get_names(std::vector *list) { assert(list != NULL); list->reserve(list->size() + BUILTIN_COUNT); for (size_t i = 0; i < BUILTIN_COUNT; i++) { append_completion(list, builtin_datas[i].name); } } /// Return a one-line description of the specified builtin. wcstring builtin_get_desc(const wcstring &name) { wcstring result; const builtin_data_t *builtin = builtin_lookup(name); if (builtin) { result = _(builtin->desc); } return result; }