Luna/shell/Command.cpp
2024-04-20 17:17:31 +02:00

154 lines
3.8 KiB
C++

/**
* @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;
}