diff --git a/Userland/Shell/AST.cpp b/Userland/Shell/AST.cpp index cbcf35384f..3d83b30193 100644 --- a/Userland/Shell/AST.cpp +++ b/Userland/Shell/AST.cpp @@ -1366,6 +1366,10 @@ void Heredoc::dump(int level) const print_indented(m_end, level + 2); print_indented("(Allows Interpolation)"sv, level + 1); print_indented(DeprecatedString::formatted("{}", m_allows_interpolation), level + 2); + if (!evaluates_to_string()) { + print_indented("(Target FD)"sv, level + 1); + print_indented(DeprecatedString::number(*m_target_fd), level + 2); + } print_indented("(Contents)"sv, level + 1); if (m_contents) m_contents->dump(level + 2); @@ -1375,29 +1379,75 @@ void Heredoc::dump(int level) const RefPtr Heredoc::run(RefPtr shell) { - if (!m_deindent) - return m_contents->run(shell); - - // To deindent, first split to lines... - auto value = m_contents->run(shell); - if (shell && shell->has_any_error()) - return make_ref_counted({}); - - if (!value) - return value; - auto list = value->resolve_as_list(shell); - // The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc - VERIFY(list.size() == 1); - auto lines = list.first().split_view('\n'); - - // Now just trim each line and put them back in a string - StringBuilder builder { list.first().length() }; - for (auto& line : lines) { - builder.append(line.trim_whitespace(TrimMode::Left)); - builder.append('\n'); + if (!m_contents) { + if (shell) + shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Attempt to evaluate an unresolved heredoc"sv, position()); + return nullptr; } - return make_ref_counted(builder.to_deprecated_string()); + auto value = [&]() -> RefPtr { + if (!m_deindent) + return m_contents->run(shell); + + // To deindent, first split to lines... + auto value = m_contents->run(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + + if (!value) + return value; + auto list = value->resolve_as_list(shell); + // The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc + VERIFY(list.size() == 1); + auto lines = list.first().split_view('\n'); + + // Now just trim each line and put them back in a string + StringBuilder builder { list.first().length() }; + for (auto& line : lines) { + builder.append(line.trim_whitespace(TrimMode::Left)); + builder.append('\n'); + } + + return make_ref_counted(builder.to_deprecated_string()); + }(); + + if (evaluates_to_string()) + return value; + + int fds[2]; + auto rc = pipe(fds); + if (rc != 0) { + // pipe() failed for {} + if (shell) + shell->raise_error(Shell::ShellError::PipeFailure, DeprecatedString::formatted("heredoc: {}", strerror(errno)), position()); + return nullptr; + } + + auto read_end = fds[0]; + auto write_end = fds[1]; + + // Dump all of 'value' into the pipe. + auto* file = fdopen(write_end, "wb"); + if (!file) { + if (shell) + shell->raise_error(Shell::ShellError::OpenFailure, "heredoc"sv, position()); + return nullptr; + } + + auto text = value->resolve_as_string(shell); + + auto written = fwrite(text.characters(), 1, text.length(), file); + fflush(file); + if (written != text.length()) { + if (shell) + shell->raise_error(Shell::ShellError::WriteFailure, "heredoc"sv, position()); + } + fclose(file); + + Command command; + command.position = position(); + command.redirections.append(FdRedirection::create(read_end, *target_fd(), Rewiring::Close::None)); + return make_ref_counted(move(command)); } void Heredoc::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) @@ -1422,11 +1472,12 @@ HitTestResult Heredoc::hit_test_position(size_t offset) const return m_contents->hit_test_position(offset); } -Heredoc::Heredoc(Position position, DeprecatedString end, bool allow_interpolation, bool deindent) +Heredoc::Heredoc(Position position, DeprecatedString end, bool allow_interpolation, bool deindent, Optional target_fd) : Node(move(position)) , m_end(move(end)) , m_allows_interpolation(allow_interpolation) , m_deindent(deindent) + , m_target_fd(target_fd) { } diff --git a/Userland/Shell/AST.h b/Userland/Shell/AST.h index bead8ad804..0f1c293ffd 100644 --- a/Userland/Shell/AST.h +++ b/Userland/Shell/AST.h @@ -1338,20 +1338,22 @@ private: class Heredoc final : public Node { public: - Heredoc(Position, DeprecatedString end, bool allow_interpolation, bool deindent); + Heredoc(Position, DeprecatedString end, bool allow_interpolation, bool deindent, Optional target_fd = {}); virtual ~Heredoc(); virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } DeprecatedString const& end() const { return m_end; } bool allow_interpolation() const { return m_allows_interpolation; } bool deindent() const { return m_deindent; } + Optional target_fd() const { return m_target_fd; } + bool evaluates_to_string() const { return !m_target_fd.has_value(); } RefPtr const& contents() const { return m_contents; } void set_contents(RefPtr contents) { m_contents = move(contents); if (m_contents->is_syntax_error()) set_is_syntax_error(m_contents->syntax_error_node()); - else + else if (is_syntax_error()) clear_syntax_error(); } @@ -1366,6 +1368,7 @@ private: DeprecatedString m_end; bool m_allows_interpolation { false }; bool m_deindent { false }; + Optional m_target_fd; RefPtr m_contents; }; diff --git a/Userland/Shell/Shell.cpp b/Userland/Shell/Shell.cpp index 23e79128b9..2a2512427a 100644 --- a/Userland/Shell/Shell.cpp +++ b/Userland/Shell/Shell.cpp @@ -2352,6 +2352,12 @@ void Shell::possibly_print_error() const case ShellError::LaunchError: warnln("Shell: {}", m_error_description); break; + case ShellError::PipeFailure: + warnln("Shell: pipe() failed for {}", m_error_description); + break; + case ShellError::WriteFailure: + warnln("Shell: write() failed for {}", m_error_description); + break; case ShellError::InternalControlFlowBreak: case ShellError::InternalControlFlowContinue: case ShellError::InternalControlFlowInterrupted: diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h index 969ffe0a29..b1ea789419 100644 --- a/Userland/Shell/Shell.h +++ b/Userland/Shell/Shell.h @@ -343,6 +343,8 @@ public: OpenFailure, OutOfMemory, LaunchError, + PipeFailure, + WriteFailure, }; void raise_error(ShellError kind, DeprecatedString description, Optional position = {})