/** * @file Command.cpp * @author apio (cloudapio.eu) * @brief Command parsing and execution. * * @copyright Copyright (c) 2024, the Luna authors. * */ #include "Command.h" #include <luna/HashMap.h> #include <os/Process.h> #include <signal.h> #include <string.h> #include <unistd.h> extern shell_builtin_t builtin_cd; extern shell_builtin_t builtin_exit; extern shell_builtin_t builtin_set; extern shell_builtin_t builtin_unset; extern shell_builtin_t builtin_echo; static HashMap<StringView, shell_builtin_t> s_builtins; Result<void> init_builtins() { s_builtins = {}; TRY(s_builtins.try_set("cd"_sv, builtin_cd)); TRY(s_builtins.try_set("exit"_sv, builtin_exit)); TRY(s_builtins.try_set("set"_sv, builtin_set)); TRY(s_builtins.try_set("unset"_sv, builtin_unset)); TRY(s_builtins.try_set("echo"_sv, builtin_echo)); return {}; } Result<Option<String>> read_command(SharedPtr<os::File> file) { auto maybe_cmd = file->read_line(); if (maybe_cmd.has_error()) { if (maybe_cmd.error() == EINTR) { os::print("\n"); return Option<String> { String {} }; } return maybe_cmd.release_error(); } auto cmd = maybe_cmd.release_value(); if (cmd.is_empty()) return Option<String> {}; if (strspn(cmd.chars(), " \n") == cmd.length()) return Option<String> { String {} }; if (strcspn(cmd.chars(), " #") == 0) return Option<String> { String {} }; return Option<String> { move(cmd) }; } Result<OwnedPtr<Command>> parse_command(StringView command) { auto vec = TRY(command.split(" \n"_sv)); auto cmd = TRY(make_owned<Command>()); cmd->args = move(vec); return cmd; } shell_builtin_t* check_builtin(Command& command) { if (!strchr(command.args[0].chars(), '/')) { return s_builtins.try_get_ref(command.args[0].view()); } return nullptr; } [[noreturn]] void execute_command(OwnedPtr<Command> command) { auto err = os::Process::exec(command->args[0].view(), command->args.slice()); os::eprintln("%s: %s", command->args[0].chars(), err.error_string()); os::Process::exit(127); } [[noreturn]] void execute_command_or_builtin(OwnedPtr<Command> command) { shell_builtin_t* builtin; if ((builtin = check_builtin(*command))) { auto rc = run_builtin(command->args, *builtin); if (rc.has_error()) os::Process::exit(rc.error()); os::Process::exit(0); } execute_command(move(command)); } Result<void> run_builtin(const Vector<String>& args, shell_builtin_t builtin) { Vector<const char*> argv; for (const auto& arg : args) { TRY(argv.try_append(arg.chars())); } TRY(argv.try_append(nullptr)); auto rc = builtin((int)args.size(), const_cast<char**>(argv.data())); if (rc.has_error()) { os::eprintln("%s: %s", args[0].chars(), rc.error_string()); return rc.release_error(); } return {}; } Result<int> execute_command_in_subprocess(OwnedPtr<Command> command, SharedPtr<os::File> input_file, bool interactive) { shell_builtin_t* builtin; if ((builtin = check_builtin(*command))) { auto rc = run_builtin(command->args, *builtin); if (rc.has_error()) return rc.error(); return 0; } pid_t child = TRY(os::Process::fork()); if (child == 0) { if (interactive) { setpgid(0, 0); tcsetpgrp(input_file->fd(), getpid()); } execute_command(move(command)); } int status; TRY(os::Process::wait(child, &status)); if (interactive) tcsetpgrp(input_file->fd(), getpgid(0)); if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig)); else os::print("\n"); } return status; }