Esempio n. 1
0
/// 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();
}
Esempio n. 2
0
/// 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;
}
Esempio n. 3
0
/// 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;
}