// 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, wchar_t ** args ) // // 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 #include #include #include "builtin.h" #include "builtin_commandline.h" #include "builtin_complete.h" #include "builtin_jobs.h" #include "builtin_printf.h" #include "builtin_set.h" #include "builtin_set_color.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 "expand.h" #include "fallback.h" // IWYU pragma: keep #include "function.h" #include "highlight.h" #include "history.h" #include "input.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 "wcstringutil.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); } // Here follows the definition of all builtin commands. The function names are all of the form // builtin_NAME where NAME is the name of the builtin. so the function name for the builtin 'fg' is // 'builtin_fg'. // // A few builtins, including 'while', 'command' and 'builtin' are not defined here as they are // handled directly by the parser. (They are not parsed as commands, instead they only alter the // parser state) // // The builtins 'break' and 'continue' are so closely related that they share the same // implementation, namely 'builtin_break_continue. // // Several other builtins, including jobs, ulimit and set are so big that they have been given their // own module. These files are all named 'builtin_NAME.cpp', where NAME is the name of the builtin. /// List a single key binding. /// Returns false if no binding with that sequence and mode exists. static bool builtin_bind_list_one(const wcstring &seq, const wcstring &bind_mode, io_streams_t &streams) { std::vector ecmds; wcstring sets_mode; if (!input_mapping_get(seq, bind_mode, &ecmds, &sets_mode)) { return false; } streams.out.append(L"bind"); // Append the mode flags if applicable. if (bind_mode != DEFAULT_BIND_MODE) { const wcstring emode = escape_string(bind_mode, ESCAPE_ALL); streams.out.append(L" -M "); streams.out.append(emode); } if (sets_mode != bind_mode) { const wcstring esets_mode = escape_string(sets_mode, ESCAPE_ALL); streams.out.append(L" -m "); streams.out.append(esets_mode); } // Append the name. wcstring tname; if (input_terminfo_get_name(seq, &tname)) { // Note that we show -k here because we have an input key name. streams.out.append_format(L" -k %ls", tname.c_str()); } else { // No key name, so no -k; we show the escape sequence directly. const wcstring eseq = escape_string(seq, ESCAPE_ALL); streams.out.append_format(L" %ls", eseq.c_str()); } // Now show the list of commands. for (size_t i = 0; i < ecmds.size(); i++) { const wcstring &ecmd = ecmds.at(i); const wcstring escaped_ecmd = escape_string(ecmd, ESCAPE_ALL); streams.out.push_back(' '); streams.out.append(escaped_ecmd); } streams.out.push_back(L'\n'); return true; } /// List all current key bindings. static void builtin_bind_list(const wchar_t *bind_mode, io_streams_t &streams) { const std::vector lst = input_mapping_get_names(); for (std::vector::const_iterator it = lst.begin(), end = lst.end(); it != end; ++it) { if (bind_mode != NULL && bind_mode != it->mode) { continue; } builtin_bind_list_one(it->seq, it->mode, streams); } } /// Print terminfo key binding names to string buffer used for standard output. /// /// \param all if set, all terminfo key binding names will be printed. If not set, only ones that /// are defined for this terminal are printed. static void builtin_bind_key_names(int all, io_streams_t &streams) { const wcstring_list_t names = input_terminfo_get_names(!all); for (size_t i = 0; i < names.size(); i++) { const wcstring &name = names.at(i); streams.out.append_format(L"%ls\n", name.c_str()); } } /// Print all the special key binding functions to string buffer used for standard output. static void builtin_bind_function_names(io_streams_t &streams) { wcstring_list_t names = input_function_get_names(); for (size_t i = 0; i < names.size(); i++) { const wchar_t *seq = names.at(i).c_str(); streams.out.append_format(L"%ls\n", seq); } } /// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed. static int get_terminfo_sequence(const wchar_t *seq, wcstring *out_seq, io_streams_t &streams) { if (input_terminfo_get_sequence(seq, out_seq)) { return 1; } wcstring eseq = escape_string(seq, 0); if (errno == ENOENT) { streams.err.append_format(_(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str()); } else if (errno == EILSEQ) { streams.err.append_format(_(L"%ls: Key with name '%ls' does not have any mapping\n"), L"bind", eseq.c_str()); } else { streams.err.append_format(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), L"bind", eseq.c_str()); } return 0; } /// Add specified key binding. static int builtin_bind_add(const wchar_t *seq, const wchar_t *const *cmds, size_t cmds_len, const wchar_t *mode, const wchar_t *sets_mode, int terminfo, io_streams_t &streams) { if (terminfo) { wcstring seq2; if (get_terminfo_sequence(seq, &seq2, streams)) { input_mapping_add(seq2.c_str(), cmds, cmds_len, mode, sets_mode); } else { return 1; } } else { input_mapping_add(seq, cmds, cmds_len, mode, sets_mode); } return 0; } /// Erase specified key bindings /// /// @param seq /// an array of all key bindings to erase /// @param all /// if specified, _all_ key bindings will be erased /// @param mode /// if specified, only bindings from that mode will be erased. If not given /// and @c all is @c false, @c DEFAULT_BIND_MODE will be used. /// @param use_terminfo /// Whether to look use terminfo -k name /// static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int use_terminfo, io_streams_t &streams) { if (all) { const std::vector lst = input_mapping_get_names(); for (std::vector::const_iterator it = lst.begin(), end = lst.end(); it != end; ++it) { if (mode == NULL || mode == it->mode) { input_mapping_erase(it->seq, it->mode); } } return 0; } int res = 0; if (mode == NULL) mode = DEFAULT_BIND_MODE; //!OCLINT(parameter reassignment) while (*seq) { if (use_terminfo) { wcstring seq2; if (get_terminfo_sequence(*seq++, &seq2, streams)) { input_mapping_erase(seq2, mode); } else { res = 1; } } else { input_mapping_erase(*seq++, mode); } } return res; } /// The bind builtin, used for setting character sequences. static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; enum { BIND_INSERT, BIND_ERASE, BIND_KEY_NAMES, BIND_FUNCTION_NAMES }; int argc = builtin_count_args(argv); int mode = BIND_INSERT; int res = STATUS_BUILTIN_OK; int all = 0; const wchar_t *bind_mode = DEFAULT_BIND_MODE; bool bind_mode_given = false; const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE; bool sets_bind_mode_given = false; int use_terminfo = 0; w.woptind = 0; static const struct woption long_options[] = {{L"all", no_argument, 0, 'a'}, {L"erase", no_argument, 0, 'e'}, {L"function-names", no_argument, 0, 'f'}, {L"help", no_argument, 0, 'h'}, {L"key", no_argument, 0, 'k'}, {L"key-names", no_argument, 0, 'K'}, {L"mode", required_argument, 0, 'M'}, {L"sets-mode", required_argument, 0, 'm'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long_only(argc, argv, L"aehkKfM:m:", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'a': { all = 1; break; } case 'e': { mode = BIND_ERASE; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 'k': { use_terminfo = 1; break; } case 'K': { mode = BIND_KEY_NAMES; break; } case 'f': { mode = BIND_FUNCTION_NAMES; break; } case 'M': { bind_mode = w.woptarg; bind_mode_given = true; break; } case 'm': { sets_bind_mode = w.woptarg; sets_bind_mode_given = true; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } // if mode is given, but not new mode, default to new mode to mode. if (bind_mode_given && !sets_bind_mode_given) { sets_bind_mode = bind_mode; } switch (mode) { case BIND_ERASE: { if (builtin_bind_erase(&argv[w.woptind], all, bind_mode_given ? bind_mode : NULL, use_terminfo, streams)) { res = STATUS_BUILTIN_ERROR; } break; } case BIND_INSERT: { int arg_count = argc - w.woptind; if (arg_count == 0) { builtin_bind_list(bind_mode_given ? bind_mode : NULL, streams); } else if (arg_count == 1) { wcstring seq; if (use_terminfo) { if (!get_terminfo_sequence(argv[w.woptind], &seq, streams)) { res = STATUS_BUILTIN_ERROR; // get_terminfo_sequence already printed the error. break; } } else { seq = argv[w.woptind]; } if (!builtin_bind_list_one(seq, bind_mode, streams)) { res = STATUS_BUILTIN_ERROR; wcstring eseq = escape_string(argv[w.woptind], 0); if (use_terminfo) { streams.err.append_format(_(L"%ls: No binding found for key '%ls'\n"), argv[0], eseq.c_str()); } else { streams.err.append_format(_(L"%ls: No binding found for sequence '%ls'\n"), argv[0], eseq.c_str()); } } break; } else { if (builtin_bind_add(argv[w.woptind], argv + (w.woptind + 1), argc - (w.woptind + 1), bind_mode, sets_bind_mode, use_terminfo, streams)) { res = STATUS_BUILTIN_ERROR; } } break; } case BIND_KEY_NAMES: { builtin_bind_key_names(all, streams); break; } case BIND_FUNCTION_NAMES: { builtin_bind_function_names(streams); break; } default: { res = STATUS_BUILTIN_ERROR; streams.err.append_format(_(L"%ls: Invalid state\n"), argv[0]); break; } } return res; } /// The block builtin, used for temporarily blocking events. static int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; enum { UNSET, GLOBAL, LOCAL, }; int scope = UNSET; int erase = 0; int argc = builtin_count_args(argv); w.woptind = 0; static const struct woption long_options[] = {{L"erase", no_argument, 0, 'e'}, {L"local", no_argument, 0, 'l'}, {L"global", no_argument, 0, 'g'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"elgh", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 'g': { scope = GLOBAL; break; } case 'l': { scope = LOCAL; break; } case 'e': { erase = 1; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } if (erase) { if (scope != UNSET) { streams.err.append_format(_(L"%ls: Can not specify scope when removing block\n"), argv[0]); return STATUS_BUILTIN_ERROR; } if (parser.global_event_blocks.empty()) { streams.err.append_format(_(L"%ls: No blocks defined\n"), argv[0]); return STATUS_BUILTIN_ERROR; } parser.global_event_blocks.pop_front(); } else { size_t block_idx = 0; block_t *block = parser.block_at_index(block_idx); event_blockage_t eb = {}; eb.typemask = (1 << EVENT_ANY); switch (scope) { case LOCAL: { // If this is the outermost block, then we're global if (block_idx + 1 >= parser.block_count()) { block = NULL; } break; } case GLOBAL: { block = NULL; break; } case UNSET: { while (block != NULL && block->type() != FUNCTION_CALL && block->type() != FUNCTION_CALL_NO_SHADOW) { // Set it in function scope block = parser.block_at_index(++block_idx); } break; } default: { DIE("unexpected scope"); break; } } if (block) { block->event_blocks.push_front(eb); } else { parser.global_event_blocks.push_front(eb); } } return STATUS_BUILTIN_OK; } /// 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; wgetopter_t w; static const struct woption long_options[] = { {L"names", no_argument, 0, 'n'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"nh", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 'n': { list = 1; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); 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_BUILTIN_OK; } /// Implementation of the builtin emit command, used to create events. static int builtin_emit(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; int argc = builtin_count_args(argv); static const struct woption long_options[] = {{L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"h", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } if (!argv[w.woptind]) { streams.err.append_format(L"%ls: expected event name\n", argv[0]); return STATUS_BUILTIN_ERROR; } const wchar_t *eventname = argv[w.woptind]; wcstring_list_t args(argv + w.woptind + 1, argv + argc); event_fire_generic(eventname, &args); return STATUS_BUILTIN_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) { wgetopter_t w; int argc = builtin_count_args(argv); int print_path = 0; w.woptind = 0; static const struct woption long_options[] = { {L"search", no_argument, 0, 's'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"svh", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 's': case 'v': { print_path = 1; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } if (!print_path) { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_ERROR; } 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)) { streams.out.append_format(L"%ls\n", path.c_str()); ++found; } } return found ? STATUS_BUILTIN_OK : STATUS_BUILTIN_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) { wgetopter_t w; 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_BUILTIN_ERROR; } static const struct woption long_options[] = {{L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"h", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } return STATUS_BUILTIN_ERROR; } /// Return a definition of the specified function. Used by the functions builtin. static wcstring functions_def(const wcstring &name) { CHECK(!name.empty(), L""); wcstring out; wcstring desc, def; function_get_desc(name, &desc); function_get_definition(name, &def); event_t search(EVENT_ANY); search.function_name = name; std::vector ev; event_get(search, &ev); out.append(L"function "); // Typically we prefer to specify the function name first, e.g. "function foo --description bar" // But If the function name starts with a -, we'll need to output it after all the options. bool defer_function_name = (name.at(0) == L'-'); if (!defer_function_name) { out.append(escape_string(name, true)); } if (!desc.empty()) { wcstring esc_desc = escape_string(desc, true); out.append(L" --description "); out.append(esc_desc); } if (!function_get_shadow_scope(name)) { out.append(L" --no-scope-shadowing"); } for (size_t i = 0; i < ev.size(); i++) { event_t *next = ev.at(i); switch (next->type) { case EVENT_SIGNAL: { append_format(out, L" --on-signal %ls", sig2wcs(next->param1.signal)); break; } case EVENT_VARIABLE: { append_format(out, L" --on-variable %ls", next->str_param1.c_str()); break; } case EVENT_EXIT: { if (next->param1.pid > 0) append_format(out, L" --on-process-exit %d", next->param1.pid); else append_format(out, L" --on-job-exit %d", -next->param1.pid); break; } case EVENT_JOB_ID: { const job_t *j = job_get(next->param1.job_id); if (j) append_format(out, L" --on-job-exit %d", j->pgid); break; } case EVENT_GENERIC: { append_format(out, L" --on-event %ls", next->str_param1.c_str()); break; } default: { DIE("unexpected next->type"); break; } } } wcstring_list_t named = function_get_named_arguments(name); if (!named.empty()) { append_format(out, L" --argument"); for (size_t i = 0; i < named.size(); i++) { append_format(out, L" %ls", named.at(i).c_str()); } } // Output the function name if we deferred it. if (defer_function_name) { out.append(L" -- "); out.append(escape_string(name, true)); } // Output any inherited variables as `set -l` lines. std::map inherit_vars = function_get_inherit_vars(name); for (std::map::const_iterator it = inherit_vars.begin(), end = inherit_vars.end(); it != end; ++it) { wcstring_list_t lst; if (!it->second.missing()) { tokenize_variable_array(it->second, lst); } // This forced tab is crummy, but we don't know what indentation style the function uses. append_format(out, L"\n\tset -l %ls", it->first.c_str()); for (wcstring_list_t::const_iterator arg_it = lst.begin(), arg_end = lst.end(); arg_it != arg_end; ++arg_it) { wcstring earg = escape_string(*arg_it, ESCAPE_ALL); out.push_back(L' '); out.append(earg); } } // This forced tab is sort of crummy - not all functions start with a tab. append_format(out, L"\n\t%ls", def.c_str()); // Append a newline before the 'end', unless there already is one there. if (!string_suffixes_string(L"\n", def)) { out.push_back(L'\n'); } out.append(L"end\n"); return out; } /// The functions builtin, used for listing and erasing functions. static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; int i; int erase = 0; wchar_t *desc = 0; int argc = builtin_count_args(argv); int list = 0; int show_hidden = 0; int res = STATUS_BUILTIN_OK; int query = 0; int copy = 0; static const struct woption long_options[] = { {L"erase", no_argument, 0, 'e'}, {L"description", required_argument, 0, 'd'}, {L"names", no_argument, 0, 'n'}, {L"all", no_argument, 0, 'a'}, {L"help", no_argument, 0, 'h'}, {L"query", no_argument, 0, 'q'}, {L"copy", no_argument, 0, 'c'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"ed:nahqc", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'e': { erase = 1; break; } case 'd': { desc = w.woptarg; break; } case 'n': { list = 1; break; } case 'a': { show_hidden = 1; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 'q': { query = 1; break; } case 'c': { copy = 1; break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } // Erase, desc, query, copy and list are mutually exclusive. if ((erase + (!!desc) + list + query + copy) > 1) { streams.err.append_format(_(L"%ls: Invalid combination of options\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } if (erase) { int i; for (i = w.woptind; i < argc; i++) function_remove(argv[i]); return STATUS_BUILTIN_OK; } else if (desc) { wchar_t *func; if (argc - w.woptind != 1) { streams.err.append_format(_(L"%ls: Expected exactly one function name\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } func = argv[w.woptind]; if (!function_exists(func)) { streams.err.append_format(_(L"%ls: Function '%ls' does not exist\n"), argv[0], func); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } function_set_desc(func, desc); return STATUS_BUILTIN_OK; } else if (list || (argc == w.woptind)) { int is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO); size_t i; wcstring_list_t names = function_get_names(show_hidden); std::sort(names.begin(), names.end()); if (is_screen) { wcstring buff; for (i = 0; i < names.size(); i++) { buff.append(names.at(i)); buff.append(L", "); } streams.out.append(reformat_for_screen(buff)); } else { for (i = 0; i < names.size(); i++) { streams.out.append(names.at(i).c_str()); streams.out.append(L"\n"); } } return STATUS_BUILTIN_OK; } else if (copy) { wcstring current_func; wcstring new_func; if (argc - w.woptind != 2) { streams.err.append_format(_(L"%ls: Expected exactly two names (current function name, " L"and new function name)\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } current_func = argv[w.woptind]; new_func = argv[w.woptind + 1]; if (!function_exists(current_func)) { streams.err.append_format(_(L"%ls: Function '%ls' does not exist\n"), argv[0], current_func.c_str()); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } if ((wcsfuncname(new_func) != 0) || parser_keywords_is_reserved(new_func)) { streams.err.append_format(_(L"%ls: Illegal function name '%ls'\n"), argv[0], new_func.c_str()); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } // Keep things simple: don't allow existing names to be copy targets. if (function_exists(new_func)) { streams.err.append_format( _(L"%ls: Function '%ls' already exists. Cannot create copy '%ls'\n"), argv[0], new_func.c_str(), current_func.c_str()); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } if (function_copy(current_func, new_func)) return STATUS_BUILTIN_OK; return STATUS_BUILTIN_ERROR; } for (i = w.woptind; i < argc; i++) { if (!function_exists(argv[i])) res++; else { if (!query) { if (i != w.woptind) streams.out.append(L"\n"); streams.out.append(functions_def(argv[i])); } } } return res; } // 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_BUILTIN_ERROR; // 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'\x1B'; 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_BUILTIN_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_BUILTIN_ERROR; } wcstring res = wgetcwd(); if (res.empty()) { return STATUS_BUILTIN_ERROR; } streams.out.append(res); streams.out.push_back(L'\n'); return STATUS_BUILTIN_OK; } /// Defines and adds a function to the function set. Calls into `function.cpp` /// to perform all heavy lifting. /// /// @param c_args /// The arguments. Should NOT contain 'function' as the first argument as the /// parser treats it as a keyword. /// @param contents /// The function definition string /// @param definition_line_offset /// The definition line offset /// /// @return /// Returns 0 on success. /// 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) { wgetopter_t w; assert(out_err != NULL); // wgetopt expects 'function' as the first argument. Make a new wcstring_list with that // property. 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()); int argc = builtin_count_args(argv); int res = STATUS_BUILTIN_OK; wchar_t *desc = 0; std::vector events; bool has_named_arguments = false; wcstring_list_t named_arguments; wcstring_list_t inherit_vars; bool shadow_scope = true; wcstring_list_t wrap_targets; // If -a/--argument-names is specified before the function name, then the function name is the // last positional, e.g. `function -a arg1 arg2 name`. If it is specified after the function // name (or not specified at all) then the function name is the first positional. This is the // common case. bool name_is_first_positional = true; wcstring_list_t positionals; const struct woption long_options[] = {{L"description", required_argument, 0, 'd'}, {L"on-signal", required_argument, 0, 's'}, {L"on-job-exit", required_argument, 0, 'j'}, {L"on-process-exit", required_argument, 0, 'p'}, {L"on-variable", required_argument, 0, 'v'}, {L"on-event", required_argument, 0, 'e'}, {L"wraps", required_argument, 0, 'w'}, {L"help", no_argument, 0, 'h'}, {L"argument-names", no_argument, 0, 'a'}, {L"no-scope-shadowing", no_argument, 0, 'S'}, {L"inherit-variable", required_argument, 0, 'V'}, {0, 0, 0, 0}}; while (res == STATUS_BUILTIN_OK) { int opt_index = 0; // The leading - here specifies RETURN_IN_ORDER. int opt = w.wgetopt_long(argc, argv, L"-d:s:j:p:v:e:w:haSV:", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; append_format(*out_err, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); res = STATUS_BUILTIN_ERROR; break; } case 'd': { desc = w.woptarg; break; } case 's': { int sig = wcs2sig(w.woptarg); if (sig < 0) { append_format(*out_err, _(L"%ls: Unknown signal '%ls'"), argv[0], w.woptarg); res = STATUS_BUILTIN_ERROR; break; } events.push_back(event_t::signal_event(sig)); break; } case 'v': { if (wcsvarname(w.woptarg)) { append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], w.woptarg); res = STATUS_BUILTIN_ERROR; break; } 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; wchar_t *end; event_t e(EVENT_ANY); if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { int 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"), argv[0]); res = STATUS_BUILTIN_ERROR; } else { e.type = EVENT_JOB_ID; e.param1.job_id = job_id; } } else { errno = 0; pid = fish_wcstoi(w.woptarg, &end, 10); if (errno || !end || *end) { append_format(*out_err, _(L"%ls: Invalid process id %ls"), argv[0], w.woptarg); res = STATUS_BUILTIN_ERROR; break; } e.type = EVENT_EXIT; e.param1.pid = (opt == 'j' ? -1 : 1) * abs(pid); } if (res == STATUS_BUILTIN_OK) { events.push_back(e); } break; } case 'a': { has_named_arguments = true; // The function name is the first positional unless -a comes before all positionals. name_is_first_positional = !positionals.empty(); break; } case 'S': { shadow_scope = false; break; } case 'w': { wrap_targets.push_back(w.woptarg); break; } case 'V': { if (wcsvarname(w.woptarg)) { append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], w.woptarg); res = STATUS_BUILTIN_ERROR; break; } inherit_vars.push_back(w.woptarg); break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 1: { assert(w.woptarg != NULL); positionals.push_back(w.woptarg); break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); res = STATUS_BUILTIN_ERROR; break; } default: { DIE("unexpected opt"); break; } } } if (res != STATUS_BUILTIN_OK) { return STATUS_BUILTIN_ERROR; } // Determine the function name, and remove it from the list of positionals. wcstring function_name; bool name_is_missing = positionals.empty(); if (!name_is_missing) { if (name_is_first_positional) { function_name = positionals.front(); positionals.erase(positionals.begin()); } else { function_name = positionals.back(); positionals.erase(positionals.end() - 1); } } if (name_is_missing) { append_format(*out_err, _(L"%ls: Expected function name"), argv[0]); res = STATUS_BUILTIN_ERROR; } else if (wcsfuncname(function_name)) { append_format(*out_err, _(L"%ls: Illegal function name '%ls'"), argv[0], function_name.c_str()); res = STATUS_BUILTIN_ERROR; } else 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"), argv[0], function_name.c_str()); res = STATUS_BUILTIN_ERROR; } else if (function_name.empty()) { append_format(*out_err, _(L"%ls: No function name given"), argv[0]); res = STATUS_BUILTIN_ERROR; } else { if (has_named_arguments) { // All remaining positionals are named arguments. named_arguments.swap(positionals); for (size_t i = 0; i < named_arguments.size(); i++) { if (wcsvarname(named_arguments.at(i))) { append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], named_arguments.at(i).c_str()); res = STATUS_BUILTIN_ERROR; break; } } } else if (!positionals.empty()) { // +1 because we already got the function name. append_format(*out_err, _(L"%ls: Expected one argument, got %lu"), argv[0], (unsigned long)(positionals.size() + 1)); res = STATUS_BUILTIN_ERROR; } } if (res != STATUS_BUILTIN_OK) { return res; } // Here we 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. for (size_t w = 0; w < wrap_targets.size(); w++) { complete_add_wrapper(function_name, wrap_targets.at(w)); } return res; } /// The random builtin generates random numbers. static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv) { static int seeded = 0; static struct drand48_data seed_buffer; int argc = builtin_count_args(argv); wgetopter_t w; static const struct woption long_options[] = {{L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"h", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); break; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } int arg_count = argc - w.woptind; if (arg_count == 0) { long res; if (!seeded) { seeded = 1; srand48_r(time(0), &seed_buffer); } lrand48_r(&seed_buffer, &res); streams.out.append_format(L"%ld\n", res % 32768); } else if (arg_count == 1) { long foo; wchar_t *end = 0; errno = 0; foo = wcstol(argv[w.woptind], &end, 10); if (errno || *end) { streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"), argv[0], argv[w.woptind]); return STATUS_BUILTIN_ERROR; } seeded = 1; srand48_r(foo, &seed_buffer); } else { streams.err.append_format(_(L"%ls: Expected zero or one argument, got %d\n"), argv[0], argc - w.woptind); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } return STATUS_BUILTIN_OK; } /// The read builtin. Reads from stdin and stores the values in environment variables. static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; wcstring buff; int i, argc = builtin_count_args(argv); int place = ENV_USER; const wchar_t *prompt = DEFAULT_READ_PROMPT; const wchar_t *right_prompt = L""; const wchar_t *commandline = L""; int exit_res = STATUS_BUILTIN_OK; const wchar_t *mode_name = READ_MODE_NAME; int nchars = 0; wchar_t *end; int shell = 0; int array = 0; bool split_null = false; while (1) { static const struct woption long_options[] = {{L"export", no_argument, 0, 'x'}, {L"global", no_argument, 0, 'g'}, {L"local", no_argument, 0, 'l'}, {L"universal", no_argument, 0, 'U'}, {L"unexport", no_argument, 0, 'u'}, {L"prompt", required_argument, 0, 'p'}, {L"right-prompt", required_argument, 0, 'R'}, {L"command", required_argument, 0, 'c'}, {L"mode-name", required_argument, 0, 'm'}, {L"nchars", required_argument, 0, 'n'}, {L"shell", no_argument, 0, 's'}, {L"array", no_argument, 0, 'a'}, {L"null", no_argument, 0, 'z'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"xglUup:R:c:hm:n:saz", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case L'x': { place |= ENV_EXPORT; break; } case L'g': { place |= ENV_GLOBAL; break; } case L'l': { place |= ENV_LOCAL; break; } case L'U': { place |= ENV_UNIVERSAL; break; } case L'u': { place |= ENV_UNEXPORT; break; } case L'p': { prompt = w.woptarg; break; } case L'R': { right_prompt = w.woptarg; break; } case L'c': { commandline = w.woptarg; break; } case L'm': { mode_name = w.woptarg; break; } case L'n': { errno = 0; nchars = fish_wcstoi(w.woptarg, &end, 10); if (errno || *end != 0) { if (errno == ERANGE) { streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"), argv[0], w.woptarg); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], w.woptarg); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } break; } case 's': { shell = 1; break; } case 'a': { array = 1; break; } case L'z': { split_null = true; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case L'?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } if ((place & ENV_UNEXPORT) && (place & ENV_EXPORT)) { streams.err.append_format(BUILTIN_ERR_EXPUNEXP, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } if ((place & ENV_LOCAL ? 1 : 0) + (place & ENV_GLOBAL ? 1 : 0) + (place & ENV_UNIVERSAL ? 1 : 0) > 1) { streams.err.append_format(BUILTIN_ERR_GLOCAL, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } if (array && w.woptind + 1 != argc) { streams.err.append_format(_(L"%ls: --array option requires a single variable name.\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } // Verify all variable names. for (i = w.woptind; i < argc; i++) { wchar_t *src; if (!wcslen(argv[i])) { streams.err.append_format(BUILTIN_ERR_VARNAME_ZERO, argv[0]); return STATUS_BUILTIN_ERROR; } for (src = argv[i]; *src; src++) { if ((!iswalnum(*src)) && (*src != L'_')) { streams.err.append_format(BUILTIN_ERR_VARCHAR, argv[0], *src); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } } } // The call to reader_readline may change woptind, so we save it away here. i = w.woptind; // Check if we should read interactively using \c reader_readline(). if (isatty(0) && streams.stdin_fd == STDIN_FILENO && !split_null) { const wchar_t *line; reader_push(mode_name); reader_set_left_prompt(prompt); reader_set_right_prompt(right_prompt); if (shell) { reader_set_complete_function(&complete); reader_set_highlight_function(&highlight_shell); reader_set_test_function(&reader_shell_test); } // No autosuggestions or abbreviations in builtin_read. reader_set_allow_autosuggesting(false); reader_set_expand_abbreviations(false); reader_set_exit_on_interrupt(true); reader_set_buffer(commandline, wcslen(commandline)); proc_push_interactive(1); event_fire_generic(L"fish_prompt"); line = reader_readline(nchars); proc_pop_interactive(); if (line) { if (0 < nchars && (size_t)nchars < wcslen(line)) { // Line may be longer than nchars if a keybinding used `commandline -i` // note: we're deliberately throwing away the tail of the commandline. // It shouldn't be unread because it was produced with `commandline -i`, // not typed. buff = wcstring(line, nchars); } else { buff = wcstring(line); } } else { exit_res = STATUS_BUILTIN_ERROR; } reader_pop(); } else { int eof = 0; buff.clear(); while (1) { int finished = 0; wchar_t res = 0; mbstate_t state = {}; while (!finished) { char b; if (read_blocked(streams.stdin_fd, &b, 1) <= 0) { eof = 1; break; } if (MB_CUR_MAX == 1) // single-byte locale { res = (unsigned char)b; finished = 1; } else { size_t sz = mbrtowc(&res, &b, 1, &state); if (sz == (size_t)-1) { memset(&state, 0, sizeof(state)); } else if (sz != (size_t)-2) { finished = 1; } } } if (eof) break; if (!split_null && res == L'\n') break; if (split_null && res == L'\0') break; buff.push_back(res); if (0 < nchars && (size_t)nchars <= buff.size()) { break; } } if (buff.empty() && eof) { exit_res = STATUS_BUILTIN_ERROR; } } if (i == argc || exit_res != STATUS_BUILTIN_OK) { return exit_res; } env_var_t ifs = env_get_string(L"IFS"); if (ifs.missing_or_empty()) { // Every character is a separate token. size_t bufflen = buff.size(); if (array) { if (bufflen > 0) { wcstring chars(bufflen + (bufflen - 1), ARRAY_SEP); wcstring::iterator out = chars.begin(); for (wcstring::const_iterator it = buff.begin(), end = buff.end(); it != end; ++it) { *out = *it; out += 2; } env_set(argv[i], chars.c_str(), place); } else { env_set(argv[i], NULL, place); } } else { // not array size_t j = 0; for (; i + 1 < argc; ++i) { if (j < bufflen) { wchar_t buffer[2] = {buff[j++], 0}; env_set(argv[i], buffer, place); } else { env_set(argv[i], L"", place); } } if (i < argc) env_set(argv[i], &buff[j], place); } } else if (array) { wcstring tokens; tokens.reserve(buff.size()); bool empty = true; for (wcstring_range loc = wcstring_tok(buff, ifs); loc.first != wcstring::npos; loc = wcstring_tok(buff, ifs, loc)) { if (!empty) tokens.push_back(ARRAY_SEP); tokens.append(buff, loc.first, loc.second); empty = false; } env_set(argv[i], empty ? NULL : tokens.c_str(), place); } else { // not array wcstring_range loc = wcstring_range(0, 0); while (i < argc) { loc = wcstring_tok(buff, (i + 1 < argc) ? ifs : wcstring(), loc); env_set(argv[i], loc.first == wcstring::npos ? L"" : &buff.c_str()[loc.first], place); ++i; } } return exit_res; } /// The status builtin. Gives various status information on fish. static int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wgetopter_t w; enum { NORMAL, IS_SUBST, IS_BLOCK, IS_INTERACTIVE, IS_LOGIN, IS_FULL_JOB_CONTROL, IS_INTERACTIVE_JOB_CONTROL, IS_NO_JOB_CONTROL, STACK_TRACE, DONE, CURRENT_FILENAME, CURRENT_LINE_NUMBER }; int mode = NORMAL; int argc = builtin_count_args(argv); int res = STATUS_BUILTIN_OK; const struct woption long_options[] = { {L"help", no_argument, 0, 'h'}, {L"is-command-substitution", no_argument, 0, 'c'}, {L"is-block", no_argument, 0, 'b'}, {L"is-interactive", no_argument, 0, 'i'}, {L"is-login", no_argument, 0, 'l'}, {L"is-full-job-control", no_argument, &mode, IS_FULL_JOB_CONTROL}, {L"is-interactive-job-control", no_argument, &mode, IS_INTERACTIVE_JOB_CONTROL}, {L"is-no-job-control", no_argument, &mode, IS_NO_JOB_CONTROL}, {L"current-filename", no_argument, 0, 'f'}, {L"current-line-number", no_argument, 0, 'n'}, {L"job-control", required_argument, 0, 'j'}, {L"print-stack-trace", no_argument, 0, 't'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L":cbilfnhj:t", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'c': { mode = IS_SUBST; break; } case 'b': { mode = IS_BLOCK; break; } case 'i': { mode = IS_INTERACTIVE; break; } case 'l': { mode = IS_LOGIN; break; } case 'f': { mode = CURRENT_FILENAME; break; } case 'n': { mode = CURRENT_LINE_NUMBER; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case 'j': { if (wcscmp(w.woptarg, L"full") == 0) job_control_mode = JOB_CONTROL_ALL; else if (wcscmp(w.woptarg, L"interactive") == 0) job_control_mode = JOB_CONTROL_INTERACTIVE; else if (wcscmp(w.woptarg, L"none") == 0) job_control_mode = JOB_CONTROL_NONE; else { streams.err.append_format(L"%ls: Invalid job control mode '%ls'\n", L"status", w.woptarg); res = STATUS_BUILTIN_ERROR; } mode = DONE; break; } case 't': { mode = STACK_TRACE; break; } case ':': { builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } default: { DIE("unexpected opt"); break; } } } if (res == STATUS_BUILTIN_ERROR) { return res; } switch (mode) { case DONE: { return STATUS_BUILTIN_OK; } case CURRENT_FILENAME: { const wchar_t *fn = parser.current_filename(); if (!fn) fn = _(L"Standard input"); streams.out.append_format(L"%ls\n", fn); return STATUS_BUILTIN_OK; } case CURRENT_LINE_NUMBER: { streams.out.append_format(L"%d\n", parser.get_lineno()); return STATUS_BUILTIN_OK; } case IS_INTERACTIVE: { return !is_interactive_session; } case IS_SUBST: { return !is_subshell; } case IS_BLOCK: { return !is_block; } case IS_LOGIN: { return !is_login; } case IS_FULL_JOB_CONTROL: { return job_control_mode != JOB_CONTROL_ALL; } case IS_INTERACTIVE_JOB_CONTROL: { return job_control_mode != JOB_CONTROL_INTERACTIVE; } case IS_NO_JOB_CONTROL: { return job_control_mode != JOB_CONTROL_NONE; } case STACK_TRACE: { streams.out.append(parser.stack_trace()); return STATUS_BUILTIN_OK; } case NORMAL: { if (is_login) { streams.out.append_format(_(L"This is a login shell\n")); } else { streams.out.append_format(_(L"This is not a login shell\n")); } streams.out.append_format( _(L"Job control: %ls\n"), job_control_mode == JOB_CONTROL_INTERACTIVE ? _(L"Only on interactive jobs") : (job_control_mode == JOB_CONTROL_NONE ? _(L"Never") : _(L"Always"))); streams.out.append(parser.stack_trace()); return STATUS_BUILTIN_OK; } default: { break; } } DIE("status subcommand not handled"); } /// 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(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } long ec; if (argc == 1) { ec = proc_get_last_status(); } else { wchar_t *end; errno = 0; ec = wcstol(argv[1], &end, 10); if (errno || *end != 0) { 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_BUILTIN_ERROR; } } 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; int res = STATUS_BUILTIN_OK; 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]); } } 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()); } res = 1; } else 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()); } res = 1; } else if (!env_set_pwd()) { res = 1; streams.err.append_format(_(L"%ls: Could not set PWD variable\n"), argv[0]); } return res; } /// 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) { wgetopter_t w; int argc; argc = builtin_count_args(argv); wchar_t *needle; bool should_output_index = false; const struct woption long_options[] = { {L"help", no_argument, 0, 'h'}, {L"index", no_argument, 0, 'i'}, {0, 0, 0, 0}}; while (1) { int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"+hi", long_options, &opt_index); if (opt == -1) break; switch (opt) { case 0: { assert(opt_index >= 0 && (size_t)opt_index < sizeof long_options / sizeof *long_options); if (long_options[opt_index].flag != 0) break; streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_BUILTIN_OK; } case ':': { builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } case 'i': { should_output_index = true; break; } default: { DIE("unexpected opt"); 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 0; } } } return 1; } /// 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_BUILTIN_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_BUILTIN_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_BUILTIN_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_BUILTIN_ERROR; } fn_intern = intern(argv[1]); } parser.push_block(new source_block_t(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(); 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; } /// Make the specified job the first job of the job list. Moving jobs around in the list makes the /// list reflect the order in which the jobs were used. static void make_first(job_t *j) { job_promote(j); } /// 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 (job_get_flag(j, JOB_CONSTRUCTED) && (!job_is_completed(j)) && ((job_is_stopped(j) || (!job_get_flag(j, JOB_FOREGROUND))) && job_get_flag(j, 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. wchar_t *endptr; int pid; int found_job = 0; errno = 0; pid = fish_wcstoi(argv[1], &endptr, 10); if (!(*endptr || errno)) { 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 { wchar_t *end; int pid; errno = 0; pid = abs(fish_wcstoi(argv[1], &end, 10)); if (*end || 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 || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j)) { streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid); j = 0; } else if (!job_get_flag(j, 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_BUILTIN_ERROR; } 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()); make_first(j); job_set_flag(j, JOB_FOREGROUND, 1); job_continue(j, job_is_stopped(j)); return STATUS_BUILTIN_OK; } /// Helper function for builtin_bg(). static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j, const wchar_t *name) { if (j == 0) { streams.err.append_format(_(L"%ls: Unknown job '%ls'\n"), L"bg", name); builtin_print_help(parser, streams, L"bg", streams.err); return STATUS_BUILTIN_ERROR; } else if (!job_get_flag(j, 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_BUILTIN_ERROR; } streams.err.append_format(_(L"Send job %d '%ls' to background\n"), j->job_id, j->command_wcstr()); make_first(j); job_set_flag(j, JOB_FOREGROUND, 0); job_continue(j, job_is_stopped(j)); return STATUS_BUILTIN_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_BUILTIN_OK; if (argv[1] == 0) { job_t *j; job_iterator_t jobs; while ((j = jobs.next())) { if (job_is_stopped(j) && job_get_flag(j, 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_BUILTIN_ERROR; } else { res = send_to_bg(parser, streams, j, _(L"(default)")); } } else { wchar_t *end; int i; int pid; for (i = 1; argv[i]; i++) { errno = 0; pid = fish_wcstoi(argv[i], &end, 10); if (errno || pid < 0 || *end || !job_get_from_pid(pid)) { streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); return STATUS_BUILTIN_ERROR; } res |= send_to_bg(parser, streams, job_get_from_pid(pid), *argv); } } 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_BUILTIN_ERROR; } // 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_BUILTIN_ERROR; } // Skip blocks interior to the loop. size_t block_idx = loop_idx; while (block_idx--) { parser.block_at_index(block_idx)->skip = true; } /* Skip the loop itself */ block_t *loop_block = parser.block_at_index(loop_idx); loop_block->skip = true; loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE; return STATUS_BUILTIN_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_BUILTIN_ERROR; } parser.push_block(new breakpoint_block_t()); reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(); 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(_(L"%ls: Too many arguments\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_BUILTIN_ERROR; } int status; if (argc == 2) { wchar_t *end; errno = 0; status = fish_wcstoi(argv[1], &end, 10); if (errno || *end != 0) { 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_BUILTIN_ERROR; } } 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_BUILTIN_ERROR; } // Skip everything up to (and then 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; } parser.block_at_index(function_block_idx)->skip = true; return status; } enum hist_cmd_t { HIST_NOOP, HIST_SEARCH, HIST_DELETE, HIST_CLEAR, HIST_MERGE, HIST_SAVE }; static hist_cmd_t hist_string_to_cmd(const wchar_t *hist_command) { if (wcscmp(hist_command, L"search") == 0) { return HIST_SEARCH; } else if (wcscmp(hist_command, L"delete") == 0) { return HIST_DELETE; } else if (wcscmp(hist_command, L"merge") == 0) { return HIST_MERGE; } else if (wcscmp(hist_command, L"save") == 0) { return HIST_SAVE; } else if (wcscmp(hist_command, L"clear") == 0) { return HIST_CLEAR; } return HIST_NOOP; } static const wcstring hist_cmd_to_string(hist_cmd_t hist_cmd) { switch (hist_cmd) { case HIST_NOOP: return L"no-op"; case HIST_SEARCH: return L"search"; case HIST_DELETE: return L"delete"; case HIST_CLEAR: return L"clear"; case HIST_MERGE: return L"merge"; case HIST_SAVE: return L"save"; } } /// Remember the history subcommand and disallow selecting more than one history subcommand. static bool set_hist_cmd(wchar_t *const cmd, hist_cmd_t *hist_cmd, hist_cmd_t sub_cmd, io_streams_t &streams) { if (*hist_cmd != HIST_NOOP) { wchar_t err_text[1024]; swprintf(err_text, sizeof(err_text) / sizeof(wchar_t), _(L"you cannot do both '%ls' and '%ls' in the same invocation"), hist_cmd_to_string(*hist_cmd).c_str(), hist_cmd_to_string(sub_cmd).c_str()); streams.err.append_format(BUILTIN_ERR_COMBO2, cmd, err_text); return false; } *hist_cmd = sub_cmd; return true; } #define CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) \ if (history_search_type_defined || show_time_format || null_terminate) { \ streams.err.append_format(_(L"%ls: you cannot use any options with the %ls command\n"), \ cmd, hist_cmd_to_string(hist_cmd).c_str()); \ status = STATUS_BUILTIN_ERROR; \ break; \ } \ if (args.size() != 0) { \ streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, \ hist_cmd_to_string(hist_cmd).c_str(), 0, args.size()); \ status = STATUS_BUILTIN_ERROR; \ break; \ } /// Manipulate history of interactive commands executed by the user. static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); hist_cmd_t hist_cmd = HIST_NOOP; history_search_type_t search_type = (history_search_type_t)-1; long max_items = LONG_MAX; bool history_search_type_defined = false; const wchar_t *show_time_format = NULL; bool case_sensitive = false; bool null_terminate = false; // TODO: Remove the long options that correspond to subcommands (e.g., '--delete') on or after // 2017-10 (which will be a full year after these flags have been deprecated). const wchar_t *short_options = L":Cmn:epchtz"; const struct woption long_options[] = {{L"prefix", no_argument, NULL, 'p'}, {L"contains", no_argument, NULL, 'c'}, {L"help", no_argument, NULL, 'h'}, {L"show-time", optional_argument, NULL, 't'}, {L"with-time", optional_argument, NULL, 't'}, {L"exact", no_argument, NULL, 'e'}, {L"max", required_argument, NULL, 'n'}, {L"null", no_argument, 0, 'z'}, {L"case-sensitive", no_argument, 0, 'C'}, {L"delete", no_argument, NULL, 1}, {L"search", no_argument, NULL, 2}, {L"save", no_argument, NULL, 3}, {L"clear", no_argument, NULL, 4}, {L"merge", no_argument, NULL, 5}, {NULL, 0, NULL, 0}}; history_t *history = reader_get_history(); // Use the default history if we have none (which happens if invoked non-interactively, e.g. // from webconfig.py. if (!history) history = &history_t::history_with_name(L"fish"); int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 1: { if (!set_hist_cmd(cmd, &hist_cmd, HIST_DELETE, streams)) { return STATUS_BUILTIN_ERROR; } break; } case 2: { if (!set_hist_cmd(cmd, &hist_cmd, HIST_SEARCH, streams)) { return STATUS_BUILTIN_ERROR; } break; } case 3: { if (!set_hist_cmd(cmd, &hist_cmd, HIST_SAVE, streams)) { return STATUS_BUILTIN_ERROR; } break; } case 4: { if (!set_hist_cmd(cmd, &hist_cmd, HIST_CLEAR, streams)) { return STATUS_BUILTIN_ERROR; } break; } case 5: { if (!set_hist_cmd(cmd, &hist_cmd, HIST_MERGE, streams)) { return STATUS_BUILTIN_ERROR; } break; } case 'C': { case_sensitive = true; break; } case 'p': { search_type = HISTORY_SEARCH_TYPE_PREFIX; history_search_type_defined = true; break; } case 'c': { search_type = HISTORY_SEARCH_TYPE_CONTAINS; history_search_type_defined = true; break; } case 'e': { search_type = HISTORY_SEARCH_TYPE_EXACT; history_search_type_defined = true; break; } case 't': { show_time_format = w.woptarg ? w.woptarg : L"# %c%n"; break; } case 'n': { wchar_t *end = 0; max_items = wcstol(w.woptarg, &end, 10); if (!(*w.woptarg != L'\0' && *end == L'\0')) { streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"), argv[0], w.woptarg); return STATUS_BUILTIN_ERROR; } break; } case 'z': { null_terminate = true; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_BUILTIN_OK; } case ':': { streams.err.append_format(BUILTIN_ERR_MISSING, cmd, argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } case '?': { // Try to parse it as a number; e.g., "-123". wchar_t *end = 0; max_items = wcstol(argv[w.woptind - 1] + 1, &end, 10); if (!(argv[w.woptind - 1][1] != L'\0' && *end == L'\0')) { streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]); return STATUS_BUILTIN_ERROR; } w.nextchar = NULL; break; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (max_items <= 0) { streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"), argv[0], w.woptarg); return STATUS_BUILTIN_ERROR; } // If a history command hasn't already been specified via a flag check the first word. // Note that this can be simplified after we eliminate allowing subcommands as flags. // See the TODO above regarding the `long_options` array. if (hist_cmd == HIST_NOOP && w.woptind < argc) { hist_cmd = hist_string_to_cmd(argv[w.woptind]); if (hist_cmd != HIST_NOOP) { w.woptind++; } } // Every argument that we haven't consumed already is an argument for a subcommand (e.g., a // search term). const wcstring_list_t args(argv + w.woptind, argv + argc); // Establish appropriate defaults. if (hist_cmd == HIST_NOOP) hist_cmd = HIST_SEARCH; if (!history_search_type_defined) { if (hist_cmd == HIST_SEARCH) search_type = HISTORY_SEARCH_TYPE_CONTAINS; if (hist_cmd == HIST_DELETE) search_type = HISTORY_SEARCH_TYPE_EXACT; } int status = STATUS_BUILTIN_OK; switch (hist_cmd) { case HIST_SEARCH: { if (!history->search(search_type, args, show_time_format, max_items, case_sensitive, null_terminate, streams)) { status = STATUS_BUILTIN_ERROR; } break; } case HIST_DELETE: { // TODO: Move this code to the history module and support the other search types // including case-insensitive matches. At this time we expect the non-exact deletions to // be handled only by the history function's interactive delete feature. if (search_type != HISTORY_SEARCH_TYPE_EXACT) { streams.err.append_format(_(L"builtin history delete only supports --exact\n")); status = STATUS_BUILTIN_ERROR; break; } if (!case_sensitive) { streams.err.append_format( _(L"builtin history delete only supports --case-sensitive\n")); status = STATUS_BUILTIN_ERROR; break; } for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter) { wcstring delete_string = *iter; if (delete_string[0] == '"' && delete_string[delete_string.length() - 1] == '"') { delete_string = delete_string.substr(1, delete_string.length() - 2); } history->remove(delete_string); } break; } case HIST_CLEAR: { CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) history->clear(); history->save(); break; } case HIST_MERGE: { CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) history->incorporate_external_changes(); break; } case HIST_SAVE: { CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) history->save(); break; } case HIST_NOOP: { DIE("Unexpected HIST_NOOP seen"); break; } } return status; } #if 0 // Disabled for the 2.2.0 release: https://github.com/fish-shell/fish-shell/issues/1809. int builtin_parse(parser_t &parser, io_streams_t &streams, wchar_t **argv) { struct sigaction act; sigemptyset(& act.sa_mask); act.sa_flags=0; act.sa_handler=SIG_DFL; sigaction(SIGINT, &act, 0); std::vector txt; for (;;) { char buff[256]; ssize_t amt = read_loop(streams.stdin_fd, buff, sizeof buff); if (amt <= 0) break; txt.insert(txt.end(), buff, buff + amt); } if (! txt.empty()) { const wcstring src = str2wcstring(&txt.at(0), txt.size()); parse_node_tree_t parse_tree; parse_error_list_t errors; bool success = parse_tree_from_string(src, parse_flag_include_comments, &parse_tree, &errors); if (! success) { streams.out.append(L"Parsing failed:\n"); for (size_t i=0; i < errors.size(); i++) { streams.out.append(errors.at(i).describe(src)); streams.out.push_back(L'\n'); } streams.out.append(L"(Reparsed with continue after error)\n"); parse_tree.clear(); errors.clear(); parse_tree_from_string(src, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, &errors); } const wcstring dump = parse_dump_tree(parse_tree, src); streams.out.append(dump); } return STATUS_BUILTIN_OK; } #endif 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)); return STATUS_BUILTIN_ERROR; } return STATUS_BUILTIN_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)); return STATUS_BUILTIN_ERROR; } return STATUS_BUILTIN_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_BUILTIN_ERROR; } 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_BUILTIN_ERROR; } streams.out.append(L"\n"); return STATUS_BUILTIN_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")}, #if 0 // Disabled for the 2.2.0 release: https://github.com/fish-shell/fish-shell/issues/1809. { L"__fish_parse", &builtin_parse, N_(L"Try out the new parser") }, #endif {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"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 bool builtin_handles_help(const wchar_t *cmd) { CHECK(cmd, 0); return contains(cmd, L"for", L"while", L"function", L"if", L"end", L"switch", L"case", L"count", L"printf"); } /// 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_BUILTIN_ERROR; 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_BUILTIN_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_BUILTIN_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; }