// Query mode. Return the number of variables that do not exist out of the specified variables. static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { int retval = 0; int scope = compute_scope(opts); for (int i = 0; i < argc; i++) { wchar_t *arg = argv[i]; wchar_t *dest = wcsdup(arg); assert(dest); std::vector<long> indexes; int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { free(dest); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } if (idx_count) { wcstring_list_t result; auto dest_str = parser.vars().get(dest, scope); if (dest_str) dest_str->to_list(result); for (auto idx : indexes) { if (idx < 1 || (size_t)idx > result.size()) retval++; } } else { if (!parser.vars().get(arg, scope)) retval++; } free(dest); } return retval; }
/// 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) { wchar_t *cmd = argv[0]; if (argv[1] != NULL) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, builtin_count_args(argv) - 1); return STATUS_INVALID_ARGS; } // If we're not interactive then we can't enter the debugger. So treat this command as a no-op. if (!shell_is_interactive()) { return STATUS_CMD_ERROR; } // Ensure we don't allow creating a breakpoint at an interactive prompt. There may be a simpler // or clearer way to do this but this works. const block_t *block1 = parser.block_at_index(1); if (!block1 || block1->type() == BREAKPOINT) { streams.err.append_format(_(L"%ls: Command not valid at an interactive prompt\n"), cmd); return STATUS_ILLEGAL_CMD; } const breakpoint_block_t *bpb = parser.push_block<breakpoint_block_t>(); reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(bpb); return proc_get_last_status(); }
void function_add(const function_data_t &data, const parser_t &parser) { ASSERT_IS_MAIN_THREAD(); CHECK(! data.name.empty(),); CHECK(data.definition,); scoped_lock lock(functions_lock); /* Remove the old function */ function_remove(data.name); /* Create and store a new function */ const wchar_t *filename = reader_current_filename(); int def_offset = -1; if (parser.current_block() != NULL) { def_offset = parser.line_number_of_character_at_offset(parser.current_block()->tok_pos); } const function_map_t::value_type new_pair(data.name, function_info_t(data, filename, def_offset, is_autoload)); loaded_functions.insert(new_pair); /* Add event handlers */ for (std::vector<event_t>::const_iterator iter = data.events.begin(); iter != data.events.end(); ++iter) { event_add_handler(*iter); } }
/// This handles the common case of setting the entire var to a set of values. static int set_var_array(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *varname, wcstring_list_t &new_values, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(cmd); UNUSED(parser); UNUSED(streams); if (opts.prepend || opts.append) { if (opts.prepend) { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } auto var_str = parser.vars().get(varname, ENV_DEFAULT); wcstring_list_t var_array; if (var_str) var_str->to_list(var_array); new_values.insert(new_values.end(), var_array.begin(), var_array.end()); if (opts.append) { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } } else { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } return STATUS_CMD_OK; }
/// This handles the more difficult case of setting individual slices of a var. static int set_var_slices(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *varname, wcstring_list_t &new_values, std::vector<long> &indexes, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(parser); if (opts.append || opts.prepend) { streams.err.append_format( L"%ls: Cannot use --append or --prepend when assigning to a slice", cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (indexes.size() != static_cast<size_t>(argc)) { streams.err.append_format(BUILTIN_SET_MISMATCHED_ARGS, cmd, indexes.size(), argc); return STATUS_INVALID_ARGS; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options const auto var_str = parser.vars().get(varname, scope); if (var_str) var_str->to_list(new_values); // Slice indexes have been calculated, do the actual work. wcstring_list_t result; for (int i = 0; i < argc; i++) result.push_back(argv[i]); int retval = update_values(new_values, indexes, result); if (retval != STATUS_CMD_OK) { streams.err.append_format(BUILTIN_SET_ARRAY_BOUNDS_ERR, cmd); return retval; } return STATUS_CMD_OK; }
/// It should search the job list for something matching the given proc. static bool find_job_by_name(const wchar_t *proc, std::vector<job_id_t> &ids, const parser_t &parser) { bool found = false; for (const auto &j : parser.jobs()) { if (j->command_is_empty()) continue; if (match_pid(j->command(), proc)) { if (!contains(ids, j->job_id)) { // If pids doesn't already have the pgid, add it. ids.push_back(j->job_id); } found = true; } // Check if the specified pid is a child process of the job. for (const process_ptr_t &p : j->processes) { if (p->actual_cmd.empty()) continue; if (match_pid(p->actual_cmd, proc)) { if (!contains(ids, j->job_id)) { // If pids doesn't already have the pgid, add it. ids.push_back(j->job_id); } found = true; } } } return found; }
/// Erase a variable. static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { if (argc != 1) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, L"--erase", 1, argc); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options wchar_t *dest = argv[0]; std::vector<long> indexes; int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } int retval; if (!valid_var_name(dest)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, dest); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (idx_count == 0) { // unset the var retval = parser.vars().remove(dest, scope); // When a non-existent-variable is unset, return ENV_NOT_FOUND as $status // but do not emit any errors at the console as a compromise between user // friendliness and correctness. if (retval != ENV_NOT_FOUND) { handle_env_return(retval, cmd, dest, streams); } } else { // remove just the specified indexes of the var const auto dest_var = parser.vars().get(dest, scope); if (!dest_var) return STATUS_CMD_ERROR; wcstring_list_t result; dest_var->to_list(result); erase_values(result, indexes); retval = env_set_reporting_errors(cmd, dest, scope, result, streams, parser.vars()); } if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, dest, streams, parser.vars()); }
static bool all_jobs_finished(const parser_t &parser) { for (const auto &j : parser.jobs()) { // If any job is not completed, return false. // If there are stopped jobs, they are ignored. if (j->is_constructed() && !j->is_completed() && !j->is_stopped()) { return false; } } return true; }
/// Print the backtrace and call for help that we use at the end of error messages. void builtin_print_error_trailer(parser_t &parser, output_stream_t &b, const wchar_t *cmd) { b.append(L"\n"); const wcstring stacktrace = parser.current_line(); // Don't print two empty lines if we don't have a stacktrace. if (!stacktrace.empty()) { b.append(stacktrace); b.append(L"\n"); } b.append_format(_(L"(Type 'help %ls' for related documentation)\n"), cmd); }
/// Set a variable. static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { if (argc == 0) { streams.err.append_format(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options wchar_t *varname = argv[0]; argv++; argc--; std::vector<long> indexes; int idx_count = parse_index(indexes, varname, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (!valid_var_name(varname)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, varname); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } int retval; wcstring_list_t new_values; if (idx_count == 0) { // Handle the simple, common, case. Set the var to the specified values. retval = set_var_array(cmd, opts, varname, new_values, argc, argv, parser, streams); } else { // Handle the uncommon case of setting specific slices of a var. retval = set_var_slices(cmd, opts, varname, new_values, indexes, argc, argv, parser, streams); } if (retval != STATUS_CMD_OK) return retval; retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams, parser.vars()); if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, varname, streams, parser.vars()); }
int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); bool resolve_symlinks = false; wgetopter_t w; int opt; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'L': resolve_symlinks = false; break; case 'P': resolve_symlinks = true; break; case 'h': builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind != argc) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1); return STATUS_INVALID_ARGS; } wcstring pwd; if (auto tmp = parser.vars().get(L"PWD")) { pwd = tmp->as_string(); } if (resolve_symlinks) { if (auto real_pwd = wrealpath(pwd)) { pwd = std::move(*real_pwd); } else { const char *error = strerror(errno); streams.err.append_format(L"%ls: realpath failed:", cmd, error); return STATUS_CMD_ERROR; } } if (pwd.empty()) { return STATUS_CMD_ERROR; } streams.out.append(pwd); streams.out.push_back(L'\n'); return STATUS_CMD_OK; }
static int wait_for_backgrounds(parser_t &parser, bool any_flag) { size_t jobs_len = parser.jobs().size(); while ((!any_flag && !all_jobs_finished(parser)) || (any_flag && !any_jobs_finished(jobs_len, parser))) { if (reader_test_interrupted()) { return 128 + SIGINT; } proc_wait_any(parser); } return 0; }
static bool any_jobs_finished(size_t jobs_len, const parser_t &parser) { bool no_jobs_running = true; // If any job is removed from list, return true. if (jobs_len != parser.jobs().size()) { return true; } for (const auto &j : parser.jobs()) { // If any job is completed, return true. if (j->is_constructed() && (j->is_completed() || j->is_stopped())) { return true; } // Check for jobs running exist or not. if (j->is_constructed() && !j->is_stopped()) { no_jobs_running = false; } } if (no_jobs_running) { return true; } return false; }
/// Show mode. Show information about the named variable(s). static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(opts); auto &vars = parser.vars(); if (argc == 0) { // show all vars wcstring_list_t names = parser.vars().get_names(ENV_USER); sort(names.begin(), names.end()); for (auto it : names) { show_scope(it.c_str(), ENV_LOCAL, streams, vars); show_scope(it.c_str(), ENV_GLOBAL, streams, vars); show_scope(it.c_str(), ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } else { for (int i = 0; i < argc; i++) { wchar_t *arg = argv[i]; if (!valid_var_name(arg)) { streams.err.append_format(_(L"$%ls: invalid var name\n"), arg); continue; } if (std::wcschr(arg, L'[')) { streams.err.append_format( _(L"%ls: `set --show` does not allow slices with the var names\n"), cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } show_scope(arg, ENV_LOCAL, streams, vars); show_scope(arg, ENV_GLOBAL, streams, vars); show_scope(arg, ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } return STATUS_CMD_OK; }
/// Return the job id to which the process with pid belongs. /// If a specified process has already finished but the job hasn't, parser_t::job_get_from_pid() /// doesn't work properly, so use this function in wait command. static job_id_t get_job_id_from_pid(pid_t pid, const parser_t &parser) { for (const auto &j : parser.jobs()) { if (j->pgid == pid) { return j->job_id; } // Check if the specified pid is a child process of the job. for (const process_ptr_t &p : j->processes) { if (p->pid == pid) { return j->job_id; } } } return 0; }
/// Print the names of all environment variables in the scope. It will include the values unless the /// `set --list` flag was used. static int builtin_set_list(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(cmd); UNUSED(argc); UNUSED(argv); UNUSED(parser); bool names_only = opts.list; wcstring_list_t names = parser.vars().get_names(compute_scope(opts)); sort(names.begin(), names.end()); for (size_t i = 0; i < names.size(); i++) { const wcstring key = names.at(i); const wcstring e_key = escape_string(key, 0); streams.out.append(e_key); if (!names_only) { auto var = parser.vars().get(key, compute_scope(opts)); if (!var.missing_or_empty()) { bool shorten = false; wcstring val = expand_escape_variable(*var); if (opts.shorten_ok && val.length() > 64) { shorten = true; val.resize(60); } streams.out.append(L" "); streams.out.append(val); if (shorten) streams.out.push_back(ellipsis_char); } } streams.out.append(L"\n"); } return STATUS_CMD_OK; }
/// 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 = (std::wcscmp(argv[0], L"break") == 0); int argc = builtin_count_args(argv); if (argc != 1) { streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } // Find the index of the enclosing for or while loop. Recall that incrementing loop_idx goes // 'up' to outer blocks. size_t loop_idx; for (loop_idx = 0; loop_idx < parser.block_count(); loop_idx++) { const block_t *b = parser.block_at_index(loop_idx); if (b->type() == WHILE || b->type() == FOR) break; } if (loop_idx >= parser.block_count()) { streams.err.append_format(_(L"%ls: Not inside of loop\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_CMD_ERROR; } // Skip blocks interior to the loop (but not the loop itself) size_t block_idx = loop_idx; while (block_idx--) { parser.block_at_index(block_idx)->skip = true; } // Mark the loop's status block_t *loop_block = parser.block_at_index(loop_idx); loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE; return STATUS_CMD_OK; }
void internal_exec_helper(parser_t &parser, parsed_source_ref_t parsed_source, tnode_t<T> node, const io_chain_t &ios) { assert(parsed_source && node && "exec_helper missing source or without node"); io_chain_t morphed_chain; std::vector<int> opened_fds; bool transmorgrified = io_transmogrify(ios, &morphed_chain, &opened_fds); // Did the transmogrification fail - if so, set error status and return. if (!transmorgrified) { proc_set_last_status(STATUS_EXEC_FAIL); return; } parser.eval_node(parsed_source, node, morphed_chain, TOP); morphed_chain.clear(); io_cleanup_fds(opened_fds); job_reap(0); }
static int expand_variables_internal(parser_t &parser, wchar_t * const in, std::vector<completion_t> &out, long last_idx) { int is_ok= 1; int empty=0; wcstring var_tmp; std::vector<long> var_idx_list; // CHECK( out, 0 ); for (long i=last_idx; (i>=0) && is_ok && !empty; i--) { const wchar_t c = in[i]; if ((c == VARIABLE_EXPAND) || (c == VARIABLE_EXPAND_SINGLE)) { long start_pos = i+1; long stop_pos; long var_len; int is_single = (c==VARIABLE_EXPAND_SINGLE); stop_pos = start_pos; while (1) { if (!(in[stop_pos ])) break; if (!(iswalnum(in[stop_pos]) || (wcschr(L"_", in[stop_pos])!= 0))) break; stop_pos++; } /* printf( "Stop for '%c'\n", in[stop_pos]);*/ var_len = stop_pos - start_pos; if (var_len == 0) { expand_variable_error(parser, in, stop_pos-1, -1); is_ok = 0; break; } var_tmp.append(in + start_pos, var_len); env_var_t var_val = expand_var(var_tmp.c_str()); if (! var_val.missing()) { int all_vars=1; wcstring_list_t var_item_list; if (is_ok) { tokenize_variable_array(var_val.c_str(), var_item_list); if (in[stop_pos] == L'[') { wchar_t *slice_end; all_vars=0; if (parse_slice(in + stop_pos, &slice_end, var_idx_list, var_item_list.size())) { parser.error(SYNTAX_ERROR, -1, L"Invalid index value"); is_ok = 0; break; } stop_pos = (slice_end-in); } if (!all_vars) { wcstring_list_t string_values(var_idx_list.size()); for (size_t j=0; j<var_idx_list.size(); j++) { long tmp = var_idx_list.at(j); /* Check that we are within array bounds. If not, truncate the list to exit. */ if (tmp < 1 || (size_t)tmp > var_item_list.size()) { parser.error(SYNTAX_ERROR, -1, ARRAY_BOUNDS_ERR); is_ok=0; var_idx_list.resize(j); break; } else { /* Replace each index in var_idx_list inplace with the string value at the specified index */ //al_set( var_idx_list, j, wcsdup((const wchar_t *)al_get( &var_item_list, tmp-1 ) ) ); string_values.at(j) = var_item_list.at(tmp-1); } } // string_values is the new var_item_list var_item_list.swap(string_values); } } if (is_ok) { if (is_single) { in[i]=0; wcstring res = in; res.push_back(INTERNAL_SEPARATOR); for (size_t j=0; j<var_item_list.size(); j++) { const wcstring &next = var_item_list.at(j); if (is_ok) { if (j != 0) res.append(L" "); res.append(next); } } res.append(in + stop_pos); is_ok &= expand_variables2(parser, res, out, i); } else { for (size_t j=0; j<var_item_list.size(); j++) { const wcstring &next = var_item_list.at(j); if (is_ok && (i == 0) && (!in[stop_pos])) { append_completion(out, next); } else { if (is_ok) { wcstring new_in; if (start_pos > 0) new_in.append(in, start_pos - 1); // at this point new_in.size() is start_pos - 1 if (start_pos>1 && new_in[start_pos-2]!=VARIABLE_EXPAND) { new_in.push_back(INTERNAL_SEPARATOR); } new_in.append(next); new_in.append(in + stop_pos); is_ok &= expand_variables2(parser, new_in, out, i); } } } } } return is_ok; } else { /* Expand a non-existing variable */ if (c == VARIABLE_EXPAND) { /* Regular expansion, i.e. expand this argument to nothing */ empty = 1; } else { /* Expansion to single argument. */ wcstring res; in[i] = 0; res.append(in); res.append(in + stop_pos); is_ok &= expand_variables2(parser, res, out, i); return is_ok; } } } } if (!empty) { append_completion(out, in); } return is_ok; }
void expand_variable_error(parser_t &parser, const wchar_t *token, size_t token_pos, int error_pos) { size_t stop_pos = token_pos+1; switch (token[stop_pos]) { case BRACKET_BEGIN: { wchar_t *cpy = wcsdup(token); *(cpy+token_pos)=0; wchar_t *name = &cpy[stop_pos+1]; wchar_t *end = wcschr(name, BRACKET_END); wchar_t *post; int is_var=0; if (end) { post = end+1; *end = 0; if (!wcsvarname(name)) { is_var = 1; } } if (is_var) { parser.error(SYNTAX_ERROR, error_pos, COMPLETE_VAR_BRACKET_DESC, cpy, name, post); } else { parser.error(SYNTAX_ERROR, error_pos, COMPLETE_VAR_BRACKET_DESC, L"", L"VARIABLE", L""); } free(cpy); break; } case INTERNAL_SEPARATOR: { parser.error(SYNTAX_ERROR, error_pos, COMPLETE_VAR_PARAN_DESC); break; } case 0: { parser.error(SYNTAX_ERROR, error_pos, COMPLETE_VAR_NULL_DESC); break; } default: { wchar_t token_stop_char = token[stop_pos]; // Unescape (see http://github.com/fish-shell/fish-shell/issues/50) if (token_stop_char == ANY_CHAR) token_stop_char = L'?'; else if (token_stop_char == ANY_STRING || token_stop_char == ANY_STRING_RECURSIVE) token_stop_char = L'*'; parser.error(SYNTAX_ERROR, error_pos, (token_stop_char == L'?' ? COMPLETE_YOU_WANT_STATUS : COMPLETE_VAR_DESC), token_stop_char); break; } } }
/** Perform cmdsubst expansion */ static int expand_cmdsubst(parser_t &parser, const wcstring &input, std::vector<completion_t> &outList) { wchar_t *paran_begin=0, *paran_end=0; std::vector<wcstring> sub_res; size_t i, j; wchar_t *tail_begin = 0; const wchar_t * const in = input.c_str(); int parse_ret; switch (parse_ret = parse_util_locate_cmdsubst(in, ¶n_begin, ¶n_end, 0)) { case -1: parser.error(SYNTAX_ERROR, -1, L"Mismatched parenthesis"); return 0; case 0: outList.push_back(completion_t(input)); return 1; case 1: break; } const wcstring subcmd(paran_begin + 1, paran_end-paran_begin - 1); if (exec_subshell(subcmd, sub_res) == -1) { parser.error(CMDSUBST_ERROR, -1, L"Unknown error while evaulating command substitution"); return 0; } tail_begin = paran_end + 1; if (*tail_begin == L'[') { std::vector<long> slice_idx; wchar_t *slice_end; if (parse_slice(tail_begin, &slice_end, slice_idx, sub_res.size())) { parser.error(SYNTAX_ERROR, -1, L"Invalid index value"); return 0; } else { std::vector<wcstring> sub_res2; tail_begin = slice_end; for (i=0; i < slice_idx.size(); i++) { long idx = slice_idx.at(i); if (idx < 1 || (size_t)idx > sub_res.size()) { parser.error(SYNTAX_ERROR, -1, ARRAY_BOUNDS_ERR); return 0; } idx = idx-1; sub_res2.push_back(sub_res.at(idx)); // debug( 0, L"Pushing item '%ls' with index %d onto sliced result", al_get( sub_res, idx ), idx ); //sub_res[idx] = 0; // ?? } sub_res = sub_res2; } } /* Recursively call ourselves to expand any remaining command substitutions. The result of this recursive call using the tail of the string is inserted into the tail_expand array list */ std::vector<completion_t> tail_expand; expand_cmdsubst(parser, tail_begin, tail_expand); /* Combine the result of the current command substitution with the result of the recursive tail expansion */ for (i=0; i<sub_res.size(); i++) { wcstring sub_item = sub_res.at(i); wcstring sub_item2 = escape_string(sub_item, 1); for (j=0; j < tail_expand.size(); j++) { wcstring whole_item; wcstring tail_item = tail_expand.at(j).completion; //sb_append_substring( &whole_item, in, len1 ); whole_item.append(in, paran_begin-in); //sb_append_char( &whole_item, INTERNAL_SEPARATOR ); whole_item.push_back(INTERNAL_SEPARATOR); //sb_append_substring( &whole_item, sub_item2, item_len ); whole_item.append(sub_item2); //sb_append_char( &whole_item, INTERNAL_SEPARATOR ); whole_item.push_back(INTERNAL_SEPARATOR); //sb_append( &whole_item, tail_item ); whole_item.append(tail_item); //al_push( out, whole_item.buff ); outList.push_back(completion_t(whole_item)); } } return 1; }
/** Perform bracket expansion */ static int expand_brackets(parser_t &parser, const wcstring &instr, int flags, std::vector<completion_t> &out) { bool syntax_error = false; int bracket_count=0; const wchar_t *bracket_begin = NULL, *bracket_end = NULL; const wchar_t *last_sep = NULL; const wchar_t *item_begin; size_t length_preceding_brackets, length_following_brackets, tot_len; const wchar_t * const in = instr.c_str(); /* Locate the first non-nested bracket pair */ for (const wchar_t *pos = in; (*pos) && !syntax_error; pos++) { switch (*pos) { case BRACKET_BEGIN: { if (bracket_count == 0) bracket_begin = pos; bracket_count++; break; } case BRACKET_END: { bracket_count--; if (bracket_count < 0) { syntax_error = true; } else if (bracket_count == 0) { bracket_end = pos; break; } } case BRACKET_SEP: { if (bracket_count == 1) last_sep = pos; } } } if (bracket_count > 0) { if (!(flags & ACCEPT_INCOMPLETE)) { syntax_error = true; } else { /* The user hasn't typed an end bracket yet; make one up and append it, then expand that. */ wcstring mod; if (last_sep) { mod.append(in, bracket_begin-in+1); mod.append(last_sep+1); mod.push_back(BRACKET_END); } else { mod.append(in); mod.push_back(BRACKET_END); } return expand_brackets(parser, mod, 1, out); } } if (syntax_error) { parser.error(SYNTAX_ERROR, -1, _(L"Mismatched brackets")); return 0; } if (bracket_begin == NULL) { append_completion(out, instr); return 1; } length_preceding_brackets = (bracket_begin-in); length_following_brackets = wcslen(bracket_end)-1; tot_len = length_preceding_brackets+length_following_brackets; item_begin = bracket_begin+1; for (const wchar_t *pos =(bracket_begin+1); true; pos++) { if (bracket_count == 0) { if ((*pos == BRACKET_SEP) || (pos==bracket_end)) { assert(pos >= item_begin); size_t item_len = pos-item_begin; wcstring whole_item; whole_item.reserve(tot_len + item_len + 2); whole_item.append(in, length_preceding_brackets); whole_item.append(item_begin, item_len); whole_item.append(bracket_end + 1); expand_brackets(parser, whole_item, flags, out); item_begin = pos+1; if (pos == bracket_end) break; } } if (*pos == BRACKET_BEGIN) { bracket_count++; } if (*pos == BRACKET_END) { bracket_count--; } } return 1; }
/** The complete builtin. Used for specifying programmable tab-completions. Calls the functions in complete.c for any heavy lifting. Defined in builtin_complete.c */ static int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); wgetopter_t w; bool res=false; int argc=0; int result_mode=SHARED; int remove = 0; int authoritative = -1; wcstring short_opt; wcstring_list_t gnu_opt, old_opt; const wchar_t *comp=L"", *desc=L"", *condition=L""; bool do_complete = false; wcstring do_complete_param; wcstring_list_t cmd; wcstring_list_t path; wcstring_list_t wrap_targets; static int recursion_level=0; argc = builtin_count_args(argv); w.woptind=0; while (! res) { static const struct woption long_options[] = { { L"exclusive", no_argument, 0, 'x' }, { L"no-files", no_argument, 0, 'f' }, { L"require-parameter", no_argument, 0, 'r' }, { L"path", required_argument, 0, 'p' }, { L"command", required_argument, 0, 'c' }, { L"short-option", required_argument, 0, 's' }, { L"long-option", required_argument, 0, 'l' }, { L"old-option", required_argument, 0, 'o' }, { L"description", required_argument, 0, 'd' }, { L"arguments", required_argument, 0, 'a' }, { L"erase", no_argument, 0, 'e' }, { L"unauthoritative", no_argument, 0, 'u' }, { L"authoritative", no_argument, 0, 'A' }, { L"condition", required_argument, 0, 'n' }, { L"wraps", required_argument, 0, 'w' }, { L"do-complete", optional_argument, 0, 'C' }, { L"help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; int opt_index = 0; int opt = w.wgetopt_long(argc, argv, L"a:c:p:s:l:o:d:frxeuAn:C::w: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); res = true; break; case 'x': result_mode |= EXCLUSIVE; break; case 'f': result_mode |= NO_FILES; break; case 'r': result_mode |= NO_COMMON; break; case 'p': case 'c': { wcstring tmp; if (unescape_string(w.woptarg, &tmp, UNESCAPE_SPECIAL)) { if (opt=='p') path.push_back(tmp); else cmd.push_back(tmp); } else { streams.err.append_format(L"%ls: Invalid token '%ls'\n", argv[0], w.woptarg); res = true; } break; } case 'd': desc = w.woptarg; break; case 'u': authoritative=0; break; case 'A': authoritative=1; break; case 's': short_opt.append(w.woptarg); break; case 'l': gnu_opt.push_back(w.woptarg); break; case 'o': old_opt.push_back(w.woptarg); break; case 'a': comp = w.woptarg; break; case 'e': remove = 1; break; case 'n': condition = w.woptarg; break; case 'w': wrap_targets.push_back(w.woptarg); break; case 'C': { do_complete = true; const wchar_t *arg = w.woptarg ? w.woptarg : reader_get_buffer(); if (arg == NULL) { // This corresponds to using 'complete -C' in non-interactive mode // See #2361 builtin_missing_argument(parser, streams, argv[0], argv[w.woptind-1]); return STATUS_BUILTIN_ERROR; } do_complete_param = arg; break; } case 'h': builtin_print_help(parser, streams, argv[0], streams.out); return 0; case '?': builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]); res = true; break; } } if (!res) { if (condition && wcslen(condition)) { const wcstring condition_string = condition; parse_error_list_t errors; if (parse_util_detect_errors(condition_string, &errors, false /* do not accept incomplete */)) { streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", argv[0], condition); for (size_t i=0; i < errors.size(); i++) { streams.err.append_format(L"\n%s: ", argv[0]); streams.err.append(errors.at(i).describe(condition_string)); } res = true; } } } if (!res) { if (comp && wcslen(comp)) { wcstring prefix; if (argv[0]) { prefix.append(argv[0]); prefix.append(L": "); } wcstring err_text; if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) { streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", argv[0], comp); streams.err.append(err_text); streams.err.push_back(L'\n'); res = true; } } } if (!res) { if (do_complete) { const wchar_t *token; parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0); /* Create a scoped transient command line, so that bulitin_commandline will see our argument, not the reader buffer */ builtin_commandline_scoped_transient_t temp_buffer(do_complete_param); if (recursion_level < 1) { recursion_level++; std::vector<completion_t> comp; complete(do_complete_param, comp, COMPLETION_REQUEST_DEFAULT); for (size_t i=0; i< comp.size() ; i++) { const completion_t &next = comp.at(i); /* Make a fake commandline, and then apply the completion to it. */ const wcstring faux_cmdline = token; size_t tmp_cursor = faux_cmdline.size(); wcstring faux_cmdline_with_completion = completion_apply_to_command_line(next.completion, next.flags, faux_cmdline, &tmp_cursor, false); /* completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE is set. We don't want to set COMPLETE_NO_SPACE because that won't close quotes. What we want is to close the quote, but not append the space. So we just look for the space and clear it. */ if (!(next.flags & COMPLETE_NO_SPACE) && string_suffixes_string(L" ", faux_cmdline_with_completion)) { faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - 1); } /* The input data is meant to be something like you would have on the command line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So we need to unescape the command line. See #1127 */ unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT); streams.out.append(faux_cmdline_with_completion); /* Append any description */ if (! next.description.empty()) { streams.out.push_back(L'\t'); streams.out.append(next.description); } streams.out.push_back(L'\n'); } recursion_level--; } } else if (w.woptind != argc) { streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); res = true; } else if (cmd.empty() && path.empty()) { /* No arguments specified, meaning we print the definitions of * all specified completions to stdout.*/ streams.out.append(complete_print()); } else { int flags = COMPLETE_AUTO_SPACE; if (remove) { builtin_complete_remove(cmd, path, short_opt.c_str(), gnu_opt, old_opt); } else { builtin_complete_add(cmd, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, authoritative, condition, comp, desc, flags); } // Handle wrap targets (probably empty) // We only wrap commands, not paths for (size_t w=0; w < wrap_targets.size(); w++) { const wcstring &wrap_target = wrap_targets.at(w); for (size_t i=0; i < cmd.size(); i++) { (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), wrap_target); } } } } return res ? 1 : 0; }
/// 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. int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); help_only_cmd_opts_t opts; int optind; int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } env_var_t dir_in; wcstring dir; if (argv[optind]) { dir_in = env_var_t(L"", argv[optind]); // unamed var } else { auto maybe_dir_in = env_get(L"HOME"); if (maybe_dir_in.missing_or_empty()) { streams.err.append_format(_(L"%ls: Could not find home directory\n"), cmd); return STATUS_CMD_ERROR; } dir_in = std::move(*maybe_dir_in); } if (!path_get_cdpath(dir_in, &dir)) { if (errno == ENOTDIR) { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.as_string().c_str()); } else if (errno == ENOENT) { streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), cmd, dir_in.as_string().c_str()); } else if (errno == EROTTEN) { streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"), cmd, dir_in.as_string().c_str()); } else { streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"), cmd, dir_in.as_string().c_str()); } if (!shell_is_interactive()) streams.err.append(parser.current_line()); return STATUS_CMD_ERROR; } if (wchdir(dir) != 0) { struct stat buffer; int status; status = wstat(dir, &buffer); if (!status && S_ISDIR(buffer.st_mode)) { streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), cmd, dir.c_str()); } else { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir.c_str()); } if (!shell_is_interactive()) { streams.err.append(parser.current_line()); } return STATUS_CMD_ERROR; } if (!env_set_pwd()) { streams.err.append_format(_(L"%ls: Could not set PWD variable\n"), cmd); return STATUS_CMD_ERROR; } return STATUS_CMD_OK; }
/// The status builtin. Gives various status information on fish. int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); status_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } // If a status 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. if (optind < argc) { status_cmd_t subcmd = str_to_enum(argv[optind], status_enum_map, status_enum_map_len); if (subcmd != STATUS_UNDEF) { if (!set_status_cmd(cmd, opts, subcmd, streams)) { return STATUS_CMD_ERROR; } optind++; } else { streams.err.append_format(BUILTIN_ERR_INVALID_SUBCMD, cmd, argv[1]); return STATUS_INVALID_ARGS; } } // Every argument that we haven't consumed already is an argument for a subcommand. const wcstring_list_t args(argv + optind, argv + argc); switch (opts.status_cmd) { case STATUS_UNDEF: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) 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()); break; } case STATUS_SET_JOB_CONTROL: { if (opts.new_job_control_mode != -1) { // Flag form was used. CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) } else { if (args.size() != 1) { const wchar_t *subcmd_str = enum_to_str(opts.status_cmd, status_enum_map); streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 1, args.size()); return STATUS_INVALID_ARGS; } opts.new_job_control_mode = job_control_str_to_mode(args[0].c_str(), cmd, streams); if (opts.new_job_control_mode == -1) { return STATUS_CMD_ERROR; } } job_control_mode = opts.new_job_control_mode; break; } case STATUS_FEATURES: { print_features(streams); break; } case STATUS_TEST_FEATURE: { if (args.size() != 1) { const wchar_t *subcmd_str = enum_to_str(opts.status_cmd, status_enum_map); streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 1, args.size()); return STATUS_INVALID_ARGS; } const auto *metadata = features_t::metadata_for(args.front().c_str()); if (!metadata) { retval = TEST_FEATURE_NOT_RECOGNIZED; } else { retval = feature_test(metadata->flag) ? TEST_FEATURE_ON : TEST_FEATURE_OFF; } break; } case STATUS_FILENAME: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) const wchar_t *fn = parser.current_filename(); if (!fn) fn = _(L"Standard input"); streams.out.append_format(L"%ls\n", fn); break; } case STATUS_FUNCTION: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) const wchar_t *fn = parser.get_function_name(opts.level); if (!fn) fn = _(L"Not a function"); streams.out.append_format(L"%ls\n", fn); break; } case STATUS_LINE_NUMBER: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) // TBD is how to interpret the level argument when fetching the line number. // See issue #4161. // streams.out.append_format(L"%d\n", parser.get_lineno(opts.level)); streams.out.append_format(L"%d\n", parser.get_lineno()); break; } case STATUS_IS_INTERACTIVE: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_interactive_session; break; } case STATUS_IS_COMMAND_SUB: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_subshell; break; } case STATUS_IS_BLOCK: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_block; break; } case STATUS_IS_BREAKPOINT: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_breakpoint; break; } case STATUS_IS_LOGIN: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_login; break; } case STATUS_IS_FULL_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_ALL; break; } case STATUS_IS_INTERACTIVE_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_INTERACTIVE; break; } case STATUS_IS_NO_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_NONE; break; } case STATUS_STACK_TRACE: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) streams.out.append(parser.stack_trace()); break; } case STATUS_CURRENT_CMD: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) // HACK: Go via the deprecated variable to get the command. const auto var = env_get(L"_"); if (!var.missing_or_empty()) { streams.out.append(var->as_string()); streams.out.push_back(L'\n'); } else { streams.out.append(program_name); streams.out.push_back(L'\n'); } break; } case STATUS_FISH_PATH: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) streams.out.append(str2wcstring(get_executable_path("fish"))); streams.out.push_back(L'\n'); break; } } return retval; }
/// Execute a block node or function "process". /// \p user_ios contains the list of user-specified ios, used so we can avoid stomping on them with /// our pipes. \return true on success, false on error. static bool exec_block_or_func_process(parser_t &parser, job_t *j, process_t *p, const io_chain_t &user_ios, io_chain_t io_chain) { assert((p->type == INTERNAL_FUNCTION || p->type == INTERNAL_BLOCK_NODE) && "Unexpected process type"); // Create an output buffer if we're piping to another process. shared_ptr<io_buffer_t> block_output_io_buffer{}; if (!p->is_last_in_job) { // Be careful to handle failure, e.g. too many open fds. block_output_io_buffer = io_buffer_t::create(STDOUT_FILENO, user_ios); if (!block_output_io_buffer) { job_mark_process_as_failed(j, p); return false; } else { // This looks sketchy, because we're adding this io buffer locally - they // aren't in the process or job redirection list. Therefore select_try won't // be able to read them. However we call block_output_io_buffer->read() // below, which reads until EOF. So there's no need to select on this. io_chain.push_back(block_output_io_buffer); } } if (p->type == INTERNAL_FUNCTION) { const wcstring func_name = p->argv0(); auto props = function_get_properties(func_name); if (!props) { debug(0, _(L"Unknown function '%ls'"), p->argv0()); return false; } const std::map<wcstring, env_var_t> inherit_vars = function_get_inherit_vars(func_name); function_block_t *fb = parser.push_block<function_block_t>(p, func_name, props->shadow_scope); function_prepare_environment(func_name, p->get_argv() + 1, inherit_vars); parser.forbid_function(func_name); internal_exec_helper(parser, props->parsed_source, props->body_node, io_chain); parser.allow_function(); parser.pop_block(fb); } else { assert(p->type == INTERNAL_BLOCK_NODE); assert(p->block_node_source && p->internal_block_node && "Process is missing node info"); internal_exec_helper(parser, p->block_node_source, p->internal_block_node, io_chain); } int status = proc_get_last_status(); // Handle output from a block or function. This usually means do nothing, but in the // case of pipes, we have to buffer such io, since otherwise the internal pipe // buffer might overflow. if (!block_output_io_buffer.get()) { // No buffer, so we exit directly. This means we have to manually set the exit // status. if (p->is_last_in_job) { proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status); } p->completed = 1; return true; } // Here we must have a non-NULL block_output_io_buffer. assert(block_output_io_buffer.get() != NULL); io_chain.remove(block_output_io_buffer); block_output_io_buffer->read(); const std::string buffer_contents = block_output_io_buffer->buffer().newline_serialized(); const char *buffer = buffer_contents.data(); size_t count = buffer_contents.size(); if (count > 0) { // We don't have to drain threads here because our child process is simple. const char *fork_reason = p->type == INTERNAL_BLOCK_NODE ? "internal block io" : "internal function io"; if (!fork_child_for_process(j, p, io_chain, false, fork_reason, [&] { exec_write_and_exit(block_output_io_buffer->fd, buffer, count, status); })) { return false; } } else { if (p->is_last_in_job) { proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status); } p->completed = 1; } return true; }
/// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current /// context. int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); help_only_cmd_opts_t opts; int optind; int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } int fd; struct stat buf; const wchar_t *fn, *fn_intern; if (argc == optind || wcscmp(argv[optind], L"-") == 0) { // Either a bare `source` which means to implicitly read from stdin or an explicit `-`. if (argc == optind && !streams.stdin_is_directly_redirected) { // Don't implicitly read from the terminal. return STATUS_CMD_ERROR; } fn = L"-"; fn_intern = fn; fd = dup(streams.stdin_fd); } else { if ((fd = wopen_cloexec(argv[optind], O_RDONLY)) == -1) { streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(cmd, streams); return STATUS_CMD_ERROR; } if (fstat(fd, &buf) == -1) { close(fd); streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(L"source", streams); return STATUS_CMD_ERROR; } if (!S_ISREG(buf.st_mode)) { close(fd); streams.err.append_format(_(L"%ls: '%ls' is not a file\n"), cmd, argv[optind]); return STATUS_CMD_ERROR; } fn_intern = intern(argv[optind]); } const source_block_t *sb = parser.push_block<source_block_t>(fn_intern); reader_push_current_filename(fn_intern); // This is slightly subtle. If this is a bare `source` with no args then `argv + optind` already // points to the end of argv. Otherwise we want to skip the file name to get to the args if any. env_set_argv(argv + optind + (argc == optind ? 0 : 1)); retval = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(sb); if (retval != STATUS_CMD_OK) { streams.err.append_format(_(L"%ls: Error while reading file '%ls'\n"), cmd, fn_intern == intern_static(L"-") ? L"<stdin>" : fn_intern); } else { retval = 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 retval; }
/// The block builtin, used for temporarily blocking events. int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); block_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } if (opts.erase) { if (opts.scope != UNSET) { streams.err.append_format(_(L"%ls: Can not specify scope when removing block\n"), cmd); return STATUS_INVALID_ARGS; } if (parser.global_event_blocks.empty()) { streams.err.append_format(_(L"%ls: No blocks defined\n"), cmd); return STATUS_CMD_ERROR; } parser.global_event_blocks.pop_front(); return STATUS_CMD_OK; } size_t block_idx = 0; block_t *block = parser.block_at_index(block_idx); event_blockage_t eb = {}; eb.typemask = (1 << EVENT_ANY); switch (opts.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_CMD_OK; }
/** The complete builtin. Used for specifying programmable tab-completions. Calls the functions in complete.c for any heavy lifting. Defined in builtin_complete.c */ static int builtin_complete(parser_t &parser, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); bool res=false; int argc=0; int result_mode=SHARED; int remove = 0; int authoritative = -1; wcstring short_opt; wcstring_list_t gnu_opt, old_opt; const wchar_t *comp=L"", *desc=L"", *condition=L""; bool do_complete = false; wcstring do_complete_param; wcstring_list_t cmd; wcstring_list_t path; static int recursion_level=0; argc = builtin_count_args(argv); woptind=0; while (! res) { static const struct woption long_options[] = { { L"exclusive", no_argument, 0, 'x' } , { L"no-files", no_argument, 0, 'f' } , { L"require-parameter", no_argument, 0, 'r' } , { L"path", required_argument, 0, 'p' } , { L"command", required_argument, 0, 'c' } , { L"short-option", required_argument, 0, 's' } , { L"long-option", required_argument, 0, 'l' } , { L"old-option", required_argument, 0, 'o' } , { L"description", required_argument, 0, 'd' } , { L"arguments", required_argument, 0, 'a' } , { L"erase", no_argument, 0, 'e' } , { L"unauthoritative", no_argument, 0, 'u' } , { L"authoritative", no_argument, 0, 'A' } , { L"condition", required_argument, 0, 'n' } , { L"do-complete", optional_argument, 0, 'C' } , { L"help", no_argument, 0, 'h' } , { 0, 0, 0, 0 } } ; int opt_index = 0; int opt = wgetopt_long(argc, argv, L"a:c:p:s:l:o:d:frxeuAn:C::h", 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); res = true; break; case 'x': result_mode |= EXCLUSIVE; break; case 'f': result_mode |= NO_FILES; break; case 'r': result_mode |= NO_COMMON; break; case 'p': case 'c': { wcstring tmp; if (unescape_string(woptarg, &tmp, UNESCAPE_SPECIAL)) { if (opt=='p') path.push_back(tmp); else cmd.push_back(tmp); } else { append_format(stderr_buffer, L"%ls: Invalid token '%ls'\n", argv[0], woptarg); res = true; } break; } case 'd': desc = woptarg; break; case 'u': authoritative=0; break; case 'A': authoritative=1; break; case 's': short_opt.append(woptarg); break; case 'l': gnu_opt.push_back(woptarg); break; case 'o': old_opt.push_back(woptarg); break; case 'a': comp = woptarg; break; case 'e': remove = 1; break; case 'n': condition = woptarg; break; case 'C': do_complete = true; do_complete_param = woptarg ? woptarg : reader_get_buffer(); break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return 0; case '?': builtin_unknown_option(parser, argv[0], argv[woptind-1]); res = true; break; } } if (!res) { if (condition && wcslen(condition)) { const wcstring condition_string = condition; parse_error_list_t errors; if (parse_util_detect_errors(condition_string, &errors)) { append_format(stderr_buffer, L"%ls: Condition '%ls' contained a syntax error", argv[0], condition); for (size_t i=0; i < errors.size(); i++) { append_format(stderr_buffer, L"\n%s: ", argv[0]); stderr_buffer.append(errors.at(i).describe(condition_string)); } res = true; } } } if (!res) { if (comp && wcslen(comp)) { if (parser.test_args(comp, 0, 0)) { append_format(stderr_buffer, L"%ls: Completion '%ls' contained a syntax error\n", argv[0], comp); parser.test_args(comp, &stderr_buffer, argv[0]); res = true; } } } if (!res) { if (do_complete) { const wchar_t *token; parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0); const wchar_t *prev_temporary_buffer = temporary_buffer; temporary_buffer = do_complete_param.c_str(); if (recursion_level < 1) { recursion_level++; std::vector<completion_t> comp; complete(do_complete_param, comp, COMPLETION_REQUEST_DEFAULT); for (size_t i=0; i< comp.size() ; i++) { const completion_t &next = comp.at(i); const wchar_t *prepend; if (next.flags & COMPLETE_REPLACES_TOKEN) { prepend = L""; } else { prepend = token; } if (!(next.description).empty()) { append_format(stdout_buffer, L"%ls%ls\t%ls\n", prepend, next.completion.c_str(), next.description.c_str()); } else { append_format(stdout_buffer, L"%ls%ls\n", prepend, next.completion.c_str()); } } recursion_level--; } temporary_buffer = prev_temporary_buffer; } else if (woptind != argc) { append_format(stderr_buffer, _(L"%ls: Too many arguments\n"), argv[0]); builtin_print_help(parser, argv[0], stderr_buffer); res = true; } else if (cmd.empty() && path.empty()) { /* No arguments specified, meaning we print the definitions of * all specified completions to stdout.*/ complete_print(stdout_buffer); } else { int flags = COMPLETE_AUTO_SPACE; if (remove) { builtin_complete_remove(cmd, path, short_opt.c_str(), gnu_opt, old_opt); } else { builtin_complete_add(cmd, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, authoritative, condition, comp, desc, flags); } } } return res ? 1 : 0; }
/// The complete builtin. Used for specifying programmable tab-completions. Calls the functions in // complete.cpp for any heavy lifting. int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); static int recursion_level = 0; wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); int result_mode = SHARED; int remove = 0; wcstring short_opt; wcstring_list_t gnu_opt, old_opt; const wchar_t *comp = L"", *desc = L"", *condition = L""; bool do_complete = false; wcstring do_complete_param; wcstring_list_t cmd_to_complete; wcstring_list_t path; wcstring_list_t wrap_targets; bool preserve_order = false; static const wchar_t *const short_options = L":a:c:p:s:l:o:d:frxeuAn:C::w:hk"; static const struct woption long_options[] = {{L"exclusive", no_argument, NULL, 'x'}, {L"no-files", no_argument, NULL, 'f'}, {L"require-parameter", no_argument, NULL, 'r'}, {L"path", required_argument, NULL, 'p'}, {L"command", required_argument, NULL, 'c'}, {L"short-option", required_argument, NULL, 's'}, {L"long-option", required_argument, NULL, 'l'}, {L"old-option", required_argument, NULL, 'o'}, {L"description", required_argument, NULL, 'd'}, {L"arguments", required_argument, NULL, 'a'}, {L"erase", no_argument, NULL, 'e'}, {L"unauthoritative", no_argument, NULL, 'u'}, {L"authoritative", no_argument, NULL, 'A'}, {L"condition", required_argument, NULL, 'n'}, {L"wraps", required_argument, NULL, 'w'}, {L"do-complete", optional_argument, NULL, 'C'}, {L"help", no_argument, NULL, 'h'}, {L"keep-order", no_argument, NULL, 'k'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'x': { result_mode |= EXCLUSIVE; break; } case 'f': { result_mode |= NO_FILES; break; } case 'r': { result_mode |= NO_COMMON; break; } case 'k': { preserve_order = true; break; } case 'p': case 'c': { wcstring tmp; if (unescape_string(w.woptarg, &tmp, UNESCAPE_SPECIAL)) { if (opt == 'p') path.push_back(tmp); else cmd_to_complete.push_back(tmp); } else { streams.err.append_format(_(L"%ls: Invalid token '%ls'\n"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } break; } case 'd': { desc = w.woptarg; break; } case 'u': { // This option was removed in commit 1911298 and is now a no-op. break; } case 'A': { // This option was removed in commit 1911298 and is now a no-op. break; } case 's': { short_opt.append(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -s requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'l': { gnu_opt.push_back(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -l requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'o': { old_opt.push_back(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -o requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'a': { comp = w.woptarg; break; } case 'e': { remove = 1; break; } case 'n': { condition = w.woptarg; break; } case 'w': { wrap_targets.push_back(w.woptarg); break; } case 'C': { do_complete = true; const wchar_t *arg = w.woptarg ? w.woptarg : reader_get_buffer(); if (arg == NULL) { // This corresponds to using 'complete -C' in non-interactive mode. // See #2361. builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } do_complete_param = arg; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind != argc) { streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (condition && std::wcslen(condition)) { const wcstring condition_string = condition; parse_error_list_t errors; if (parse_util_detect_errors(condition_string, &errors, false /* do not accept incomplete */)) { streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd, condition); for (size_t i = 0; i < errors.size(); i++) { streams.err.append_format(L"\n%s: ", cmd); streams.err.append(errors.at(i).describe(condition_string)); } return STATUS_CMD_ERROR; } } if (comp && std::wcslen(comp)) { wcstring prefix; prefix.append(cmd); prefix.append(L": "); wcstring err_text; if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) { streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", cmd, comp); streams.err.append(err_text); streams.err.push_back(L'\n'); return STATUS_CMD_ERROR; } } if (do_complete) { const wchar_t *token; parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0); // Create a scoped transient command line, so that bulitin_commandline will see our // argument, not the reader buffer. builtin_commandline_scoped_transient_t temp_buffer(do_complete_param); if (recursion_level < 1) { recursion_level++; std::vector<completion_t> comp; complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH, parser.vars()); for (size_t i = 0; i < comp.size(); i++) { const completion_t &next = comp.at(i); // Make a fake commandline, and then apply the completion to it. const wcstring faux_cmdline = token; size_t tmp_cursor = faux_cmdline.size(); wcstring faux_cmdline_with_completion = completion_apply_to_command_line( next.completion, next.flags, faux_cmdline, &tmp_cursor, false); // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE // is set. We don't want to set COMPLETE_NO_SPACE because that won't close // quotes. What we want is to close the quote, but not append the space. So we // just look for the space and clear it. if (!(next.flags & COMPLETE_NO_SPACE) && string_suffixes_string(L" ", faux_cmdline_with_completion)) { faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - 1); } // The input data is meant to be something like you would have on the command // line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So // we need to unescape the command line. See #1127. unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT); streams.out.append(faux_cmdline_with_completion); // Append any description. if (!next.description.empty()) { streams.out.push_back(L'\t'); streams.out.append(next.description); } streams.out.push_back(L'\n'); } recursion_level--; } } else if (cmd_to_complete.empty() && path.empty()) { // No arguments specified, meaning we print the definitions of all specified completions // to stdout. streams.out.append(complete_print()); } else { int flags = COMPLETE_AUTO_SPACE; if (preserve_order) { flags |= COMPLETE_DONT_SORT; } if (remove) { builtin_complete_remove(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt); } else { builtin_complete_add(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, condition, comp, desc, flags); } // Handle wrap targets (probably empty). We only wrap commands, not paths. for (size_t w = 0; w < wrap_targets.size(); w++) { const wcstring &wrap_target = wrap_targets.at(w); for (size_t i = 0; i < cmd_to_complete.size(); i++) { (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd_to_complete.at(i), wrap_target); } } } return STATUS_CMD_OK; }