/// 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(); }
/// Sets up appropriate signal handlers. void signal_set_handlers() { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); // Ignore SIGPIPE. We'll detect failed writes and deal with them appropriately. We don't want // this signal interrupting other syscalls or terminating us. act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, 0); // Whether or not we're interactive we want SIGCHLD to not interrupt restartable syscalls. act.sa_flags = SA_SIGINFO; act.sa_sigaction = &handle_chld; act.sa_flags = SA_SIGINFO | SA_RESTART; if (sigaction(SIGCHLD, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } if (shell_is_interactive()) { set_interactive_handlers(); } else { set_non_interactive_handlers(); } }
wcstring parser_t::current_line() { if (execution_contexts.empty()) { return wcstring(); } const parse_execution_context_t *context = execution_contexts.back().get(); assert(context != NULL); int source_offset = context->get_current_source_offset(); if (source_offset < 0) { return wcstring(); } const int lineno = this->get_lineno(); const wchar_t *file = this->current_filename(); wcstring prefix; // If we are not going to print a stack trace, at least print the line number and filename. if (!shell_is_interactive() || is_function()) { if (file) { append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(), lineno); } else if (is_within_fish_initialization) { append_format(prefix, L"%ls (line %d): ", _(L"Startup"), lineno); } else { append_format(prefix, L"%ls (line %d): ", _(L"Standard input"), lineno); } } bool is_interactive = shell_is_interactive(); bool skip_caret = is_interactive && !is_function(); // Use an error with empty text. assert(source_offset >= 0); parse_error_t empty_error = {}; empty_error.source_start = source_offset; wcstring line_info = empty_error.describe_with_prefix(context->get_source(), prefix, is_interactive, skip_caret); if (!line_info.empty()) { line_info.push_back(L'\n'); } line_info.append(this->stack_trace()); return line_info; }
// Check if we are setting a uvar and a global of the same name exists. See // https://github.com/fish-shell/fish-shell/issues/806 static int check_global_scope_exists(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *dest, io_streams_t &streams, const environment_t &vars) { if (opts.universal) { auto global_dest = vars.get(dest, ENV_GLOBAL); if (global_dest && shell_is_interactive()) { streams.err.append_format(BUILTIN_SET_UVAR_ERR, cmd, dest); } } return STATUS_CMD_OK; }
void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const { assert(output != NULL); if (!errors.empty()) { const parse_error_t &err = errors.at(0); const bool is_interactive = shell_is_interactive(); // Determine if we want to try to print a caret to point at the source error. The // err.source_start <= src.size() check is due to the nasty way that slices work, which is // by rewriting the source. size_t which_line = 0; bool skip_caret = true; if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size()) { // Determine which line we're on. which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n'); // Don't include the caret if we're interactive, this is the first line of text, and our // source is at its beginning, because then it's obvious. skip_caret = (is_interactive && which_line == 1 && err.source_start == 0); } wcstring prefix; const wchar_t *filename = this->current_filename(); if (filename) { if (which_line > 0) { prefix = format_string(_(L"%ls (line %lu): "), user_presentable_path(filename).c_str(), which_line); } else { prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str()); } } else { prefix = L"fish: "; } const wcstring description = err.describe_with_prefix(src, prefix, is_interactive, skip_caret); if (!description.empty()) { output->append(description); output->push_back(L'\n'); } output->append(this->stack_trace()); } }
/// 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; }
/// Sets appropriate signal handlers. void signal_set_handlers() { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; act.sa_sigaction = &default_handler; // First reset everything to a use default_handler, a function whose sole action is to fire of // an event. sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); sigaction(SIGTSTP, &act, 0); sigaction(SIGTTIN, &act, 0); sigaction(SIGTTOU, &act, 0); sigaction(SIGCHLD, &act, 0); // Ignore sigpipe, which we may get from the universal variable notifier. sigaction(SIGPIPE, &act, 0); if (shell_is_interactive()) { // Interactive mode. Ignore interactive signals. We are a shell, we know what is best for // the user. act.sa_handler = SIG_IGN; sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); sigaction(SIGTSTP, &act, 0); sigaction(SIGTTIN, &act, 0); sigaction(SIGTTOU, &act, 0); act.sa_sigaction = &handle_int; act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } act.sa_sigaction = &handle_chld; act.sa_flags = SA_SIGINFO; if (sigaction(SIGCHLD, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } #ifdef SIGWINCH act.sa_flags = SA_SIGINFO; act.sa_sigaction = &handle_winch; if (sigaction(SIGWINCH, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } #endif act.sa_flags = SA_SIGINFO; act.sa_sigaction = &handle_hup; if (sigaction(SIGHUP, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } // SIGTERM restores the terminal controlling process before dying. act.sa_flags = SA_SIGINFO; act.sa_sigaction = &handle_term; if (sigaction(SIGTERM, &act, 0)) { wperror(L"sigaction"); FATAL_EXIT(); } } else { // Non-interactive. Ignore interrupt, check exit status of processes to determine result // instead. act.sa_handler = SIG_IGN; sigaction(SIGINT, &act, 0); sigaction(SIGQUIT, &act, 0); act.sa_handler = SIG_DFL; act.sa_sigaction = &handle_chld; act.sa_flags = SA_SIGINFO; if (sigaction(SIGCHLD, &act, 0)) { wperror(L"sigaction"); exit_without_destructors(1); } } }