Beispiel #1
0
// 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;
}
Beispiel #2
0
// 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;
    }
}
Beispiel #3
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;
}
Beispiel #4
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;
}
Beispiel #5
0
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;
}
Beispiel #6
0
/// 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;
}