// Validate the given path `list`. If there are any entries referring to invalid directories which // contain a colon, then complain. Return true if any path element was valid, false if not. static bool validate_path_warning_on_colons(const wchar_t *cmd, const wchar_t *key, //!OCLINT(npath complexity) const wcstring_list_t &list, io_streams_t &streams, const environment_t &vars) { // Always allow setting an empty value. if (list.empty()) return true; bool any_success = false; // Don't bother validating (or complaining about) values that are already present. When // determining already-present values, use ENV_DEFAULT instead of the passed-in scope because // in: // // set -l PATH stuff $PATH // // where we are temporarily shadowing a variable, we want to compare against the shadowed value, // not the (missing) local value. Also don't bother to complain about relative paths, which // don't start with /. wcstring_list_t existing_values; const auto existing_variable = vars.get(key, ENV_DEFAULT); if (!existing_variable.missing_or_empty()) existing_variable->to_list(existing_values); for (const wcstring &dir : list) { if (!string_prefixes_string(L"/", dir) || contains(existing_values, dir)) { any_success = true; continue; } const wchar_t *colon = std::wcschr(dir.c_str(), L':'); bool looks_like_colon_sep = colon && colon[1]; if (!looks_like_colon_sep && any_success) { // Once we have one valid entry, skip the remaining ones unless we might warn. continue; } struct stat buff; bool valid = true; if (wstat(dir, &buff) == -1) { valid = false; } else if (!S_ISDIR(buff.st_mode)) { errno = ENOTDIR; valid = false; } else if (waccess(dir, X_OK) == -1) { valid = false; } if (valid) { any_success = true; } else if (looks_like_colon_sep) { streams.err.append_format(BUILTIN_SET_PATH_ERROR, cmd, key, dir.c_str(), std::strerror(errno)); streams.err.append_format(BUILTIN_SET_PATH_HINT, cmd, key, key, std::wcschr(dir.c_str(), L':') + 1); } } return any_success; }
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. void update_wait_on_escape_ms(const environment_t &vars) { auto escape_time_ms = vars.get(L"fish_escape_delay_ms"); if (escape_time_ms.missing_or_empty()) { wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; return; } long tmp = fish_wcstol(escape_time_ms->as_string().c_str()); if (errno || tmp < 10 || tmp >= 5000) { std::fwprintf(stderr, L"ignoring fish_escape_delay_ms: value '%ls' " L"is not an integer or is < 10 or >= 5000 ms\n", escape_time_ms->as_string().c_str()); } else { wait_on_escape_ms = (int)tmp; } }
/// 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; }
/// 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; }
static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, bool apply_exit_status, bool is_subcmd) { ASSERT_IS_MAIN_THREAD(); bool prev_subshell = is_subshell; const int prev_status = proc_get_last_status(); bool split_output = false; const auto ifs = env_get(L"IFS"); if (!ifs.missing_or_empty()) { split_output = true; } is_subshell = true; int subcommand_status = -1; // assume the worst // IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may // be null. const shared_ptr<io_buffer_t> io_buffer( io_buffer_t::create(STDOUT_FILENO, io_chain_t(), is_subcmd ? read_byte_limit : 0)); if (io_buffer.get() != NULL) { parser_t &parser = parser_t::principal_parser(); if (parser.eval(cmd, io_chain_t(io_buffer), SUBST) == 0) { subcommand_status = proc_get_last_status(); } io_buffer->read(); } if (io_buffer->buffer().discarded()) subcommand_status = STATUS_READ_TOO_MUCH; // If the caller asked us to preserve the exit status, restore the old status. Otherwise set the // status of the subcommand. proc_set_last_status(apply_exit_status ? subcommand_status : prev_status); is_subshell = prev_subshell; if (lst == NULL || io_buffer.get() == NULL) { return subcommand_status; } // Walk over all the elements. for (const auto &elem : io_buffer->buffer().elements()) { if (elem.is_explicitly_separated()) { // Just append this one. lst->push_back(str2wcstring(elem.contents)); continue; } // Not explicitly separated. We have to split it explicitly. assert(!elem.is_explicitly_separated() && "should not be explicitly separated"); const char *begin = elem.contents.data(); const char *end = begin + elem.contents.size(); if (split_output) { const char *cursor = begin; while (cursor < end) { // Look for the next separator. const char *stop = (const char *)memchr(cursor, '\n', end - cursor); const bool hit_separator = (stop != NULL); if (!hit_separator) { // If it's not found, just use the end. stop = end; } // Stop now points at the first character we do not want to copy. lst->push_back(str2wcstring(cursor, stop - cursor)); // If we hit a separator, skip over it; otherwise we're at the end. cursor = stop + (hit_separator ? 1 : 0); } } else { // We're not splitting output, but we still want to trim off a trailing newline. if (end != begin && end[-1] == '\n') { --end; } lst->push_back(str2wcstring(begin, end - begin)); } } return subcommand_status; }
/// 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; }
/// set_color builtin. int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // By the time this is called we should have initialized the curses subsystem. assert(curses_initialized); // Hack in missing italics and dim capabilities omitted from MacOS xterm-256color terminfo // Helps Terminal.app/iTerm #if __APPLE__ const auto term_prog = parser.vars().get(L"TERM_PROGRAM"); if (!term_prog.missing_or_empty() && (term_prog->as_string() == L"Apple_Terminal" || term_prog->as_string() == L"iTerm.app")) { const auto term = parser.vars().get(L"TERM"); if (!term.missing_or_empty() && (term->as_string() == L"xterm-256color")) { enter_italics_mode = sitm_esc; exit_italics_mode = ritm_esc; enter_dim_mode = dim_esc; } } #endif // Variables used for parsing the argument list. wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); // Some code passes variables to set_color that don't exist, like $fish_user_whatever. As a // hack, quietly return failure. if (argc <= 1) { return EXIT_FAILURE; } const wchar_t *bgcolor = NULL; bool bold = false, underline = false, italics = false, dim = false, reverse = false; // Parse options to obtain the requested operation and the modifiers. int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'b': { bgcolor = w.woptarg; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case 'o': { bold = true; break; } case 'i': { italics = true; break; } case 'd': { dim = true; break; } case 'r': { reverse = true; break; } case 'u': { underline = true; break; } case 'c': { print_colors(streams); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } // Remaining arguments are foreground color. std::vector<rgb_color_t> fgcolors; for (; w.woptind < argc; w.woptind++) { rgb_color_t fg = rgb_color_t(argv[w.woptind]); if (fg.is_none()) { streams.err.append_format(_(L"%ls: Unknown color '%ls'\n"), argv[0], argv[w.woptind]); return STATUS_INVALID_ARGS; } fgcolors.push_back(fg); } if (fgcolors.empty() && bgcolor == NULL && !bold && !underline && !italics && !dim && !reverse) { streams.err.append_format(_(L"%ls: Expected an argument\n"), argv[0]); return STATUS_INVALID_ARGS; } // #1323: We may have multiple foreground colors. Choose the best one. If we had no foreground // color, we'll get none(); if we have at least one we expect not-none. const rgb_color_t fg = best_color(fgcolors, output_get_color_support()); assert(fgcolors.empty() || !fg.is_none()); const rgb_color_t bg = rgb_color_t(bgcolor ? bgcolor : L""); if (bgcolor && bg.is_none()) { streams.err.append_format(_(L"%ls: Unknown color '%ls'\n"), argv[0], bgcolor); return STATUS_INVALID_ARGS; } // Test if we have at least basic support for setting fonts, colors and related bits - otherwise // just give up... if (cur_term == NULL || !exit_attribute_mode) { return STATUS_CMD_ERROR; } outputter_t outp; if (bold && enter_bold_mode) { writembs_nofail(outp, tparm((char *)enter_bold_mode)); } if (underline && enter_underline_mode) { writembs_nofail(outp, enter_underline_mode); } if (italics && enter_italics_mode) { writembs_nofail(outp, enter_italics_mode); } if (dim && enter_dim_mode) { writembs_nofail(outp, enter_dim_mode); } if (reverse && enter_reverse_mode) { writembs_nofail(outp, enter_reverse_mode); } else if (reverse && enter_standout_mode) { writembs_nofail(outp, enter_standout_mode); } if (bgcolor != NULL && bg.is_normal()) { writembs_nofail(outp, tparm((char *)exit_attribute_mode)); } if (!fg.is_none()) { if (fg.is_normal() || fg.is_reset()) { writembs_nofail(outp, tparm((char *)exit_attribute_mode)); } else { if (!outp.write_color(fg, true /* is_fg */)) { // We need to do *something* or the lack of any output messes up // when the cartesian product here would make "foo" disappear: // $ echo (set_color foo)bar outp.set_color(rgb_color_t::reset(), rgb_color_t::none()); } } } if (bgcolor != NULL && !bg.is_normal() && !bg.is_reset()) { outp.write_color(bg, false /* not is_fg */); } // Output the collected string. streams.out.append(str2wcstring(outp.contents())); return STATUS_CMD_OK; }