/// 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; }
/// Return a definition of the specified function. Used by the functions builtin. static wcstring functions_def(const wcstring &name) { CHECK(!name.empty(), L""); //!OCLINT(multiple unary operator) wcstring out; wcstring desc, def; function_get_desc(name, desc); function_get_definition(name, def); std::vector<std::shared_ptr<event_handler_t>> ev = event_get_function_handlers(name); out.append(L"function "); // Typically we prefer to specify the function name first, e.g. "function foo --description bar" // But If the function name starts with a -, we'll need to output it after all the options. bool defer_function_name = (name.at(0) == L'-'); if (!defer_function_name) { out.append(escape_string(name, true)); } if (!desc.empty()) { wcstring esc_desc = escape_string(desc, true); out.append(L" --description "); out.append(esc_desc); } auto props = function_get_properties(name); assert(props && "Should have function properties"); if (!props->shadow_scope) { out.append(L" --no-scope-shadowing"); } for (const auto &next : ev) { const event_description_t &d = next->desc; switch (d.type) { case event_type_t::signal: { append_format(out, L" --on-signal %ls", sig2wcs(d.param1.signal)); break; } case event_type_t::variable: { append_format(out, L" --on-variable %ls", d.str_param1.c_str()); break; } case event_type_t::exit: { if (d.param1.pid > 0) append_format(out, L" --on-process-exit %d", d.param1.pid); else append_format(out, L" --on-job-exit %d", -d.param1.pid); break; } case event_type_t::job_exit: { const job_t *j = job_t::from_job_id(d.param1.job_id); if (j) append_format(out, L" --on-job-exit %d", j->pgid); break; } case event_type_t::generic: { append_format(out, L" --on-event %ls", d.str_param1.c_str()); break; } case event_type_t::any: default: { DIE("unexpected next->type"); break; } } } const wcstring_list_t &named = props->named_arguments; if (!named.empty()) { append_format(out, L" --argument"); for (const auto &name : named) { append_format(out, L" %ls", name.c_str()); } } // Output the function name if we deferred it. if (defer_function_name) { out.append(L" -- "); out.append(escape_string(name, true)); } // Output any inherited variables as `set -l` lines. std::map<wcstring, env_var_t> inherit_vars = function_get_inherit_vars(name); for (const auto &kv : inherit_vars) { wcstring_list_t lst; kv.second.to_list(lst); // This forced tab is crummy, but we don't know what indentation style the function uses. append_format(out, L"\n\tset -l %ls", kv.first.c_str()); for (const auto &arg : lst) { wcstring earg = escape_string(arg, ESCAPE_ALL); out.push_back(L' '); out.append(earg); } } // This forced tab is sort of crummy - not all functions start with a tab. append_format(out, L"\n\t%ls", def.c_str()); // Append a newline before the 'end', unless there already is one there. if (!string_suffixes_string(L"\n", def)) { out.push_back(L'\n'); } out.append(L"end\n"); return out; }