/** \file builtin.c 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" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fallback.h" #include "util.h" #include "wutil.h" #include "builtin.h" #include "function.h" #include "complete.h" #include "proc.h" #include "parser.h" #include "reader.h" #include "env.h" #include "common.h" #include "wgetopt.h" #include "sanity.h" #include "tokenizer.h" #include "wildcard.h" #include "input_common.h" #include "input.h" #include "intern.h" #include "event.h" #include "signal.h" #include "exec.h" #include "highlight.h" #include "parse_util.h" #include "parser_keywords.h" #include "expand.h" #include "path.h" #include "history.h" #include "parse_tree.h" /** The default prompt for the read command */ #define DEFAULT_READ_PROMPT L"set_color green; echo -n read; set_color normal; echo -n \"> \"" /** The mode name to pass to history and input */ #define READ_MODE_NAME L"fish_read" /** The send stuff to foreground message */ #define FG_MSG _( L"Send job %d, '%ls' to foreground\n" ) /** Datastructure to describe a builtin. */ struct builtin_data_t { /** Name of the builtin */ const wchar_t *name; /** Function pointer tothe builtin implementation */ int (*func)(parser_t &parser, wchar_t **argv); /** Description of what the builtin does */ const wchar_t *desc; bool operator<(const wcstring &) const; bool operator<(const builtin_data_t *) const; }; 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; } int builtin_out_redirect; int builtin_err_redirect; /* Buffers for storing the output of builtin functions */ wcstring stdout_buffer, stderr_buffer; const wcstring &get_stdout_buffer() { ASSERT_IS_MAIN_THREAD(); return stdout_buffer; } const wcstring &get_stderr_buffer() { ASSERT_IS_MAIN_THREAD(); return stderr_buffer; } void builtin_show_error(const wcstring &err) { ASSERT_IS_MAIN_THREAD(); stderr_buffer.append(err); } /** Stack containing builtin I/O for recursive builtin calls. */ struct io_stack_elem_t { int in; wcstring out; wcstring err; }; static std::stack > io_stack; /** The file from which builtin functions should attempt to read, use instead of stdin. */ static int builtin_stdin; /** The underlying IO redirections behind the current builtin. This should normally not be used - sb_out and friends are already configured to handle everything. */ static const io_chain_t *real_io; /** Counts the number of non null pointers in the specified array */ static int builtin_count_args(const wchar_t * const * argv) { int argc = 1; while (argv[argc] != NULL) { argc++; } return argc; } /** This function works like wperror, but it prints its result into the sb_err string instead of to stderr. Used by the builtin commands. */ static void builtin_wperror(const wchar_t *s) { if (s != 0) { stderr_buffer.append(s); stderr_buffer.append(L": "); } char *err = strerror(errno); if (err) { const wcstring werr = str2wcstring(err); stderr_buffer.append(werr); stderr_buffer.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; } wcstring builtin_help_get(parser_t &parser, const wchar_t *name) { /* 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); const wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str()); if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0) { for (size_t i=0; i 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') { wchar_t *pos2; int is_empty = 1; 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) { append_format(b, _(L"%ls: Type 'help %ls' for related documentation\n\n"), cmd, cmd); } free(str); } } /** Perform error reporting for encounter with unknown option */ static void builtin_unknown_option(parser_t &parser, const wchar_t *cmd, const wchar_t *opt) { append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, cmd, opt); builtin_print_help(parser, cmd, stderr_buffer); } /** Perform error reporting for encounter with missing argument */ static void builtin_missing_argument(parser_t &parser, const wchar_t *cmd, const wchar_t *opt) { append_format(stderr_buffer, BUILTIN_ERR_MISSING, cmd, opt); builtin_print_help(parser, cmd, stderr_buffer); } /* Here follows the definition of all builtin commands. The function names are all on 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 file. These files are all named 'builtin_NAME.c', where NAME is the name of the builtin. These files are included directly below. */ #include "builtin_set.cpp" #include "builtin_commandline.cpp" #include "builtin_complete.cpp" #include "builtin_ulimit.cpp" #include "builtin_jobs.cpp" #include "builtin_set_color.cpp" #include "builtin_printf.cpp" /* builtin_test lives in builtin_test.cpp */ int builtin_test(parser_t &parser, wchar_t **argv); /** List all current key bindings */ static void builtin_bind_list(const wchar_t *bind_mode) { size_t i; const wcstring_list_t lst = input_mapping_get_names(); for (i=0; i ecmds; wcstring mode; wcstring sets_mode; if (! input_mapping_get(seq, &ecmds, &mode, &sets_mode)) { continue; } if (bind_mode != NULL && bind_mode != mode) { continue; } // Append the initial 'bind' command and the name wcstring tname; if (input_terminfo_get_name(seq, &tname)) { // Note that we show -k here because we have an input key name append_format(stdout_buffer, L"bind -k %ls", tname.c_str()); } else { // No key name, so no -k; we show the escape sequence directly const wcstring eseq = escape_string(seq, 1); append_format(stdout_buffer, L"bind %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); stdout_buffer.push_back(' '); stdout_buffer.append(escaped_ecmd); } stdout_buffer.push_back(L'\n'); } } /** 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) { const wcstring_list_t names = input_terminfo_get_names(!all); for (size_t i=0; i= parser.block_count()) { block = NULL; } break; } case GLOBAL: { block=NULL; } 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); } } } 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, wchar_t **argv) { int argc=builtin_count_args(argv); int list=0; woptind=0; 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 = 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; append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; case 'n': list=1; break; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; } } if (list) { wcstring_list_t names = builtin_get_names(); sort(names.begin(), names.end()); for (size_t i=0; i 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_shadows(name)) { out.append(L" --no-scope-shadowing"); } for (size_t i=0; itype) { 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; } } } 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)); } /* 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"); } /** The functions builtin, used for listing and erasing functions. */ static int builtin_functions(parser_t &parser, wchar_t **argv) { 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; woptind=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 = 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; append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; case 'e': erase=1; break; case 'd': desc=woptarg; break; case 'n': list=1; break; case 'a': show_hidden=1; break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; case 'q': query = 1; break; case 'c': copy = 1; break; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; } } /* Erase, desc, query, copy and list are mutually exclusive */ if ((erase + (!!desc) + list + query + copy) > 1) { append_format(stderr_buffer, _(L"%ls: Invalid combination of options\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; } if (erase) { int i; for (i=woptind; i 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, wchar_t **argv) { /* 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: assert(0 && "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) { stdout_buffer.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 */ stdout_buffer.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 */ wc = narrow_val; //is this OK for conversion? } 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) { stdout_buffer.push_back(wc); } } } } if (print_newline && continue_output) { stdout_buffer.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, wchar_t **argv) { wchar_t dir_path[4096]; wchar_t *res = wgetcwd(dir_path, 4096); if (res == NULL) { return STATUS_BUILTIN_ERROR; } else { stdout_buffer.append(dir_path); stdout_buffer.push_back(L'\n'); return STATUS_BUILTIN_OK; } } /** Adds a function to the function set. It calls into function.cpp to perform any heavy lifting. */ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstring &contents, int definition_line_offset, wcstring *out_err) { 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; std::auto_ptr named_arguments(NULL); wchar_t *name = 0; bool shadows = true; woptind=0; 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"help", no_argument, 0, 'h' }, { L"argument-names", no_argument, 0, 'a' }, { L"no-scope-shadowing", no_argument, 0, 'S' }, { 0, 0, 0, 0 } }; while (1 && (!res)) { int opt_index = 0; int opt = wgetopt_long(argc, argv, L"d:s:j:p:v:e:haS", 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 = 1; break; case 'd': desc=woptarg; break; case 's': { int sig = wcs2sig(woptarg); if (sig < 0) { append_format(*out_err, _(L"%ls: Unknown signal '%ls'\n"), argv[0], woptarg); res=1; break; } events.push_back(event_t::signal_event(sig)); break; } case 'v': { if (wcsvarname(woptarg)) { append_format(*out_err, _(L"%ls: Invalid variable name '%ls'\n"), argv[0], woptarg); res=STATUS_BUILTIN_ERROR; break; } events.push_back(event_t::variable_event(woptarg)); break; } case 'e': { events.push_back(event_t::generic_event(woptarg)); break; } case 'j': case 'p': { pid_t pid; wchar_t *end; event_t e(EVENT_ANY); if ((opt == 'j') && (wcscasecmp(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\n"), argv[0]); res=1; } else { e.type = EVENT_JOB_ID; e.param1.job_id = job_id; } } else { errno = 0; pid = fish_wcstoi(woptarg, &end, 10); if (errno || !end || *end) { append_format(*out_err, _(L"%ls: Invalid process id %ls\n"), argv[0], woptarg); res=1; break; } e.type = EVENT_EXIT; e.param1.pid = (opt=='j'?-1:1)*abs(pid); } if (res) { /* nothing */ } else { events.push_back(e); } break; } case 'a': if (named_arguments.get() == NULL) named_arguments.reset(new wcstring_list_t); break; case 'S': shadows = 0; break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); res = 1; break; } } if (!res) { if (argc == woptind) { append_format(*out_err, _(L"%ls: Expected function name\n"), argv[0]); res=1; } else if (wcsfuncname(argv[woptind])) { append_format(*out_err, _(L"%ls: Illegal function name '%ls'\n"), argv[0], argv[woptind]); res=1; } else if (parser_keywords_is_reserved(argv[woptind])) { append_format(*out_err, _(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name\n"), argv[0], argv[woptind]); res=1; } else if (! wcslen(argv[woptind])) { append_format(*out_err, _(L"%ls: No function name given\n"), argv[0]); res=1; } else { name = argv[woptind++]; if (named_arguments.get()) { while (woptind < argc) { if (wcsvarname(argv[woptind])) { append_format(*out_err, _(L"%ls: Invalid variable name '%ls'\n"), argv[0], argv[woptind]); res = STATUS_BUILTIN_ERROR; break; } named_arguments->push_back(argv[woptind++]); } } else if (woptind != argc) { append_format(*out_err, _(L"%ls: Expected one argument, got %d\n"), argv[0], argc); res=1; } } } if (res) { builtin_print_help(parser, argv[0], *out_err); } else { function_data_t d; d.name = name; if (desc) d.description = desc; d.events.swap(events); d.shadows = shadows; if (named_arguments.get()) d.named_arguments.swap(*named_arguments); for (size_t i=0; i 1) { append_format(stderr_buffer, BUILTIN_ERR_GLOCAL, argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; } /* Verify all variable names */ for (i=woptind; i= 0 && (size_t)opt_index < sizeof long_options / sizeof *long_options); if (long_options[opt_index].flag != 0) break; append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], long_options[opt_index].name); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; case ':': builtin_missing_argument(parser, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; case 'i': should_output_index = true; break; } } needle = argv[woptind]; if (!needle) { append_format(stderr_buffer, _(L"%ls: Key not specified\n"), argv[0]); } for (int i=woptind+1; i2)?(argv+2):(argv+1), wcstring_list_t()); res = reader_read(fd, real_io ? *real_io : io_chain_t()); parser.pop_block(); if (res) { append_format(stderr_buffer, _(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, 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) { append_format(stderr_buffer, _(L"%ls: There are no suitable jobs\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); } } else if (argv[2] != 0) { /* Specifying what 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) { append_format(stderr_buffer, _(L"%ls: Ambiguous job\n"), argv[0]); } else { append_format(stderr_buffer, _(L"%ls: '%ls' is not a job\n"), argv[0], argv[1]); } builtin_print_help(parser, argv[0], stderr_buffer); j=0; } else { wchar_t *end; int pid; errno = 0; pid = abs(fish_wcstoi(argv[1], &end, 10)); if (*end || errno) { append_format(stderr_buffer, BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]); builtin_print_help(parser, argv[0], stderr_buffer); } else { j = job_get_from_pid(pid); if (!j || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j)) { append_format(stderr_buffer, _(L"%ls: No suitable job: %d\n"), argv[0], pid); builtin_print_help(parser, argv[0], stderr_buffer); j=0; } else if (!job_get_flag(j, JOB_CONTROL)) { append_format(stderr_buffer, _(L"%ls: Can't put job %d, '%ls' to foreground because it is not under job control\n"), argv[0], pid, j->command_wcstr()); builtin_print_help(parser, argv[0], stderr_buffer); j=0; } } } if (j) { if (builtin_err_redirect) { append_format(stderr_buffer, 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_wcstr()); if (! ft.empty()) env_set(L"_", ft.c_str(), ENV_EXPORT); reader_write_title(); make_first(j); job_set_flag(j, JOB_FOREGROUND, 1); job_continue(j, job_is_stopped(j)); } return j != 0; } /** Helper function for builtin_bg() */ static int send_to_bg(parser_t &parser, job_t *j, const wchar_t *name) { if (j == 0) { append_format(stderr_buffer, _(L"%ls: Unknown job '%ls'\n"), L"bg", name); builtin_print_help(parser, L"bg", stderr_buffer); return STATUS_BUILTIN_ERROR; } else if (!job_get_flag(j, JOB_CONTROL)) { append_format(stderr_buffer, _(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, L"bg", stderr_buffer); return STATUS_BUILTIN_ERROR; } else { append_format(stderr_buffer, _(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, 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) { append_format(stderr_buffer, _(L"%ls: There are no suitable jobs\n"), argv[0]); res = 1; } else { res = send_to_bg(parser, j, _(L"(default)")); } } else { wchar_t *end; int i; int pid; int err = 0; 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)) { append_format(stderr_buffer, _(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); err = 1; break; } } if (!err) { for (i=1; !res && argv[i]; i++) { pid = fish_wcstoi(argv[i], 0, 10); res |= send_to_bg(parser, 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, wchar_t **argv) { int is_break = (wcscmp(argv[0],L"break")==0); int argc = builtin_count_args(argv); if (argc != 1) { append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[1]); builtin_print_help(parser, argv[0], stderr_buffer); 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()) { append_format(stderr_buffer, _(L"%ls: Not inside of loop\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); 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, wchar_t **argv) { parser.push_block(new breakpoint_block_t()); reader_read(STDIN_FILENO, real_io ? *real_io : 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, wchar_t **argv) { int argc = builtin_count_args(argv); int status = proc_get_last_status(); switch (argc) { case 1: break; case 2: { wchar_t *end; errno = 0; status = fish_wcstoi(argv[1],&end,10); if (errno || *end != 0) { append_format(stderr_buffer, _(L"%ls: Argument '%ls' must be an integer\n"), argv[0], argv[1]); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; } break; } default: append_format(stderr_buffer, _(L"%ls: Too many arguments\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); return STATUS_BUILTIN_ERROR; } /* 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()) { append_format(stderr_buffer, _(L"%ls: Not inside of function\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); 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; } /** History of commands executed by user */ static int builtin_history(parser_t &parser, wchar_t **argv) { int argc = builtin_count_args(argv); bool search_history = false; bool delete_item = false; bool search_prefix = false; bool save_history = false; bool clear_history = false; bool merge_history = false; static const struct woption long_options[] = { { L"prefix", no_argument, 0, 'p' }, { L"delete", no_argument, 0, 'd' }, { L"search", no_argument, 0, 's' }, { L"contains", no_argument, 0, 'c' }, { L"save", no_argument, 0, 'v' }, { L"clear", no_argument, 0, 'l' }, { L"merge", no_argument, 0, 'm' }, { L"help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; int opt = 0; int opt_index = 0; woptind = 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"); while ((opt = wgetopt_long_only(argc, argv, L"pdscvl", long_options, &opt_index)) != -1) { switch (opt) { case 'p': search_prefix = true; break; case 'd': delete_item = true; break; case 's': search_history = true; break; case 'c': break; case 'v': save_history = true; break; case 'l': clear_history = true; break; case 'm': merge_history = true; break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; break; case EOF: /* Remainder are arguments */ break; case '?': append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; break; default: append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]); return STATUS_BUILTIN_ERROR; } } /* Everything after is an argument */ const wcstring_list_t args(argv + woptind, argv + argc); if (argc == 1) { wcstring full_history; history->get_string_representation(&full_history, wcstring(L"\n")); stdout_buffer.append(full_history); stdout_buffer.push_back('\n'); return STATUS_BUILTIN_OK; } if (merge_history) { history->incorporate_external_changes(); } if (search_history) { int res = STATUS_BUILTIN_ERROR; for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter) { const wcstring &search_string = *iter; if (search_string.empty()) { append_format(stderr_buffer, BUILTIN_ERR_COMBO2, argv[0], L"Use --search with either --contains or --prefix"); return res; } history_search_t searcher = history_search_t(*history, search_string, search_prefix?HISTORY_SEARCH_TYPE_PREFIX:HISTORY_SEARCH_TYPE_CONTAINS); while (searcher.go_backwards()) { stdout_buffer.append(searcher.current_string()); stdout_buffer.append(L"\n"); res = STATUS_BUILTIN_OK; } } return res; } if (delete_item) { 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); } return STATUS_BUILTIN_OK; } if (save_history) { history->save(); return STATUS_BUILTIN_OK; } if (clear_history) { history->clear(); history->save(); return STATUS_BUILTIN_OK; } return STATUS_BUILTIN_ERROR; } int builtin_parse(parser_t &parser, 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(builtin_stdin, 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_none, &parse_tree, &errors); if (! success) { stdout_buffer.append(L"Parsing failed:\n"); for (size_t i=0; i < errors.size(); i++) { stdout_buffer.append(errors.at(i).describe(src)); stdout_buffer.push_back(L'\n'); } stdout_buffer.append(L"(Reparsed with continue after error)\n"); parse_tree.clear(); errors.clear(); parse_tree_from_string(src, parse_flag_continue_after_error, &parse_tree, &errors); } const wcstring dump = parse_dump_tree(parse_tree, src); stdout_buffer.append(dump); } 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") }, { L"__fish_parse", &builtin_parse, N_(L"Try out the new parser") }, { 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_generic, 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"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"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"switch", &builtin_generic, N_(L"Conditionally execute a block of commands") }, { L"test", &builtin_test, N_(L"Test a condition") }, { 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) 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; } else { return NULL; } } void builtin_init() { wopterr = 0; for (size_t i=0; i < BUILTIN_COUNT; i++) { intern_static(builtin_datas[i].name); } } void builtin_destroy() { } int builtin_exists(const wcstring &cmd) { return !!builtin_lookup(cmd); } /** Return true if the specified builtin should handle it's own help, false otherwise. */ static int internal_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"); } int builtin_run(parser_t &parser, const wchar_t * const *argv, const io_chain_t &io) { int (*cmd)(parser_t &parser, const wchar_t * const *argv)=0; real_io = &io; CHECK(argv, STATUS_BUILTIN_ERROR); CHECK(argv[0], STATUS_BUILTIN_ERROR); const builtin_data_t *data = builtin_lookup(argv[0]); cmd = (int (*)(parser_t &parser, const wchar_t * const*))(data ? data->func : NULL); if (argv[1] != 0 && !internal_help(argv[0])) { if (argv[2] == 0 && (parse_util_argument_is_help(argv[1], 0))) { builtin_print_help(parser, argv[0], stdout_buffer); return STATUS_BUILTIN_OK; } } if (data != NULL) { int status; status = cmd(parser, argv); return status; } else { debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]); } return STATUS_BUILTIN_ERROR; } 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; } void builtin_get_names(std::vector &list) { for (size_t i=0; i < BUILTIN_COUNT; i++) { append_completion(list, builtin_datas[i].name); } } wcstring builtin_get_desc(const wcstring &name) { wcstring result; const builtin_data_t *builtin = builtin_lookup(name); if (builtin) { result = _(builtin->desc); } return result; } void builtin_push_io(parser_t &parser, int in) { ASSERT_IS_MAIN_THREAD(); if (builtin_stdin != -1) { struct io_stack_elem_t elem = {builtin_stdin, stdout_buffer, stderr_buffer}; io_stack.push(elem); } builtin_stdin = in; stdout_buffer.clear(); stderr_buffer.clear(); } void builtin_pop_io(parser_t &parser) { ASSERT_IS_MAIN_THREAD(); builtin_stdin = 0; if (! io_stack.empty()) { struct io_stack_elem_t &elem = io_stack.top(); stderr_buffer = elem.err; stdout_buffer = elem.out; builtin_stdin = elem.in; io_stack.pop(); } else { stdout_buffer.clear(); stderr_buffer.clear(); builtin_stdin = 0; } }