diff options
author | Andreas Kling <kling@serenityos.org> | 2021-01-12 11:53:14 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:04:07 +0100 |
commit | c4e2fd8123b83a6b890b410741f1d416a1162fdb (patch) | |
tree | be0ae8ba44fec67223809237807597273c092282 /Userland/Shell/Shell.h | |
parent | 07c7e353722d880b565188dba9f81a0449baf80c (diff) | |
download | serenity-c4e2fd8123b83a6b890b410741f1d416a1162fdb.zip |
Shell: Move to Userland/Shell/
Diffstat (limited to 'Userland/Shell/Shell.h')
-rw-r--r-- | Userland/Shell/Shell.h | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h new file mode 100644 index 0000000000..7a357ac870 --- /dev/null +++ b/Userland/Shell/Shell.h @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Job.h" +#include "Parser.h" +#include <AK/CircularQueue.h> +#include <AK/HashMap.h> +#include <AK/NonnullOwnPtrVector.h> +#include <AK/String.h> +#include <AK/StringBuilder.h> +#include <AK/Types.h> +#include <AK/Vector.h> +#include <LibCore/Notifier.h> +#include <LibCore/Object.h> +#include <LibLine/Editor.h> +#include <termios.h> + +#define ENUMERATE_SHELL_BUILTINS() \ + __ENUMERATE_SHELL_BUILTIN(alias) \ + __ENUMERATE_SHELL_BUILTIN(cd) \ + __ENUMERATE_SHELL_BUILTIN(cdh) \ + __ENUMERATE_SHELL_BUILTIN(pwd) \ + __ENUMERATE_SHELL_BUILTIN(exec) \ + __ENUMERATE_SHELL_BUILTIN(exit) \ + __ENUMERATE_SHELL_BUILTIN(export) \ + __ENUMERATE_SHELL_BUILTIN(glob) \ + __ENUMERATE_SHELL_BUILTIN(unset) \ + __ENUMERATE_SHELL_BUILTIN(history) \ + __ENUMERATE_SHELL_BUILTIN(umask) \ + __ENUMERATE_SHELL_BUILTIN(dirs) \ + __ENUMERATE_SHELL_BUILTIN(pushd) \ + __ENUMERATE_SHELL_BUILTIN(popd) \ + __ENUMERATE_SHELL_BUILTIN(setopt) \ + __ENUMERATE_SHELL_BUILTIN(shift) \ + __ENUMERATE_SHELL_BUILTIN(source) \ + __ENUMERATE_SHELL_BUILTIN(time) \ + __ENUMERATE_SHELL_BUILTIN(jobs) \ + __ENUMERATE_SHELL_BUILTIN(disown) \ + __ENUMERATE_SHELL_BUILTIN(fg) \ + __ENUMERATE_SHELL_BUILTIN(bg) \ + __ENUMERATE_SHELL_BUILTIN(wait) + +#define ENUMERATE_SHELL_OPTIONS() \ + __ENUMERATE_SHELL_OPTION(inline_exec_keep_empty_segments, false, "Keep empty segments in inline execute $(...)") \ + __ENUMERATE_SHELL_OPTION(verbose, false, "Announce every command that is about to be executed") + +namespace Shell { + +class Shell; + +class Shell : public Core::Object { + C_OBJECT(Shell); + +public: + constexpr static auto local_init_file_path = "~/.shellrc"; + constexpr static auto global_init_file_path = "/etc/shellrc"; + + bool should_format_live() const { return m_should_format_live; } + void set_live_formatting(bool value) { m_should_format_live = value; } + + void setup_signals(); + + struct SourcePosition { + String source_file; + String literal_source_text; + Optional<AST::Position> position; + }; + + int run_command(const StringView&, Optional<SourcePosition> = {}); + bool is_runnable(const StringView&); + RefPtr<Job> run_command(const AST::Command&); + NonnullRefPtrVector<Job> run_commands(Vector<AST::Command>&); + bool run_file(const String&, bool explicitly_invoked = true); + bool run_builtin(const AST::Command&, const NonnullRefPtrVector<AST::Rewiring>&, int& retval); + bool has_builtin(const StringView&) const; + void block_on_job(RefPtr<Job>); + void block_on_pipeline(RefPtr<AST::Pipeline>); + String prompt() const; + + static String expand_tilde(const String&); + static Vector<String> expand_globs(const StringView& path, StringView base); + static Vector<String> expand_globs(Vector<StringView> path_segments, const StringView& base); + Vector<AST::Command> expand_aliases(Vector<AST::Command>); + String resolve_path(String) const; + String resolve_alias(const String&) const; + + RefPtr<AST::Value> get_argument(size_t); + RefPtr<AST::Value> lookup_local_variable(const String&); + String local_variable_or(const String&, const String&); + void set_local_variable(const String&, RefPtr<AST::Value>, bool only_in_current_frame = false); + void unset_local_variable(const String&, bool only_in_current_frame = false); + + void define_function(String name, Vector<String> argnames, RefPtr<AST::Node> body); + bool has_function(const String&); + bool invoke_function(const AST::Command&, int& retval); + + String format(const StringView&, ssize_t& cursor) const; + + RefPtr<Line::Editor> editor() const { return m_editor; } + + struct LocalFrame { + LocalFrame(const String& name, HashMap<String, RefPtr<AST::Value>> variables) + : name(name) + , local_variables(move(variables)) + { + } + + String name; + HashMap<String, RefPtr<AST::Value>> local_variables; + }; + + struct Frame { + Frame(NonnullOwnPtrVector<LocalFrame>& frames, const LocalFrame& frame) + : frames(frames) + , frame(frame) + { + } + ~Frame(); + + void leak_frame() { should_destroy_frame = false; } + + private: + NonnullOwnPtrVector<LocalFrame>& frames; + const LocalFrame& frame; + bool should_destroy_frame { true }; + }; + + [[nodiscard]] Frame push_frame(String name); + void pop_frame(); + + static String escape_token_for_single_quotes(const String& token); + static String escape_token(const String& token); + static String unescape_token(const String& token); + static bool is_special(char c); + + static bool is_glob(const StringView&); + static Vector<StringView> split_path(const StringView&); + + void highlight(Line::Editor&) const; + Vector<Line::CompletionSuggestion> complete(); + Vector<Line::CompletionSuggestion> complete_path(const String& base, const String&, size_t offset); + Vector<Line::CompletionSuggestion> complete_program_name(const String&, size_t offset); + Vector<Line::CompletionSuggestion> complete_variable(const String&, size_t offset); + Vector<Line::CompletionSuggestion> complete_user(const String&, size_t offset); + Vector<Line::CompletionSuggestion> complete_option(const String&, const String&, size_t offset); + + void restore_ios(); + + u64 find_last_job_id() const; + const Job* find_job(u64 id); + const Job* current_job() const { return m_current_job; } + void kill_job(const Job*, int sig); + + String get_history_path(); + void print_path(const String& path); + + bool read_single_line(); + + void notify_child_event(); + + struct termios termios; + struct termios default_termios; + bool was_interrupted { false }; + bool was_resized { false }; + + String cwd; + String username; + String home; + + constexpr static auto TTYNameSize = 32; + constexpr static auto HostNameSize = 64; + + char ttyname[TTYNameSize]; + char hostname[HostNameSize]; + + uid_t uid; + int last_return_code { 0 }; + Vector<String> directory_stack; + CircularQueue<String, 8> cd_history; // FIXME: have a configurable cd history length + HashMap<u64, NonnullRefPtr<Job>> jobs; + Vector<String, 256> cached_path; + + String current_script; + + enum ShellEventType { + ReadLine, + }; + + enum class ShellError { + None, + InternalControlFlowBreak, + InternalControlFlowContinue, + EvaluatedSyntaxError, + NonExhaustiveMatchRules, + InvalidGlobError, + OpenFailure, + }; + + void raise_error(ShellError kind, String description, Optional<AST::Position> position = {}) + { + m_error = kind; + m_error_description = move(description); + if (m_source_position.has_value() && position.has_value()) + m_source_position.value().position = position.release_value(); + } + bool has_error(ShellError err) const { return m_error == err; } + const String& error_description() const { return m_error_description; } + ShellError take_error() + { + auto err = m_error; + m_error = ShellError::None; + m_error_description = {}; + return err; + } + void possibly_print_error() const; + bool is_control_flow(ShellError error) + { + switch (error) { + case ShellError::InternalControlFlowBreak: + case ShellError::InternalControlFlowContinue: + return true; + default: + return false; + } + } + +#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ + bool name { default_ }; + + struct Options { + ENUMERATE_SHELL_OPTIONS(); + } options; + +#undef __ENUMERATE_SHELL_OPTION + +private: + Shell(Line::Editor&); + Shell(); + virtual ~Shell() override; + + // FIXME: Port to Core::Property + void save_to(JsonObject&); + void bring_cursor_to_beginning_of_a_line() const; + + void cache_path(); + void add_entry_to_cache(const String&); + void stop_all_jobs(); + const Job* m_current_job { nullptr }; + LocalFrame* find_frame_containing_local_variable(const String& name); + + void run_tail(RefPtr<Job>); + void run_tail(const AST::Command&, const AST::NodeWithAction&, int head_exit_code); + + [[noreturn]] void execute_process(Vector<const char*>&& argv); + + virtual void custom_event(Core::CustomEvent&) override; + +#define __ENUMERATE_SHELL_BUILTIN(builtin) \ + int builtin_##builtin(int argc, const char** argv); + + ENUMERATE_SHELL_BUILTINS(); + +#undef __ENUMERATE_SHELL_BUILTIN + + constexpr static const char* builtin_names[] = { +#define __ENUMERATE_SHELL_BUILTIN(builtin) #builtin, + + ENUMERATE_SHELL_BUILTINS() + +#undef __ENUMERATE_SHELL_BUILTIN + }; + + bool m_should_ignore_jobs_on_next_exit { false }; + pid_t m_pid { 0 }; + + struct ShellFunction { + String name; + Vector<String> arguments; + RefPtr<AST::Node> body; + }; + + HashMap<String, ShellFunction> m_functions; + NonnullOwnPtrVector<LocalFrame> m_local_frames; + NonnullRefPtrVector<AST::Redirection> m_global_redirections; + + HashMap<String, String> m_aliases; + bool m_is_interactive { true }; + bool m_is_subshell { false }; + bool m_should_reinstall_signal_handlers { true }; + + ShellError m_error { ShellError::None }; + String m_error_description; + Optional<SourcePosition> m_source_position; + + bool m_should_format_live { false }; + + RefPtr<Line::Editor> m_editor; + + bool m_default_constructed { false }; + + mutable bool m_last_continuation_state { false }; // false == not needed. +}; + +static constexpr bool is_word_character(char c) +{ + return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a'); +} + +inline size_t find_offset_into_node(const String& unescaped_text, size_t escaped_offset) +{ + size_t unescaped_offset = 0; + size_t offset = 0; + for (auto& c : unescaped_text) { + if (offset == escaped_offset) + return unescaped_offset; + + if (Shell::is_special(c)) + ++offset; + ++offset; + ++unescaped_offset; + } + + return unescaped_offset; +} + +} |