Shell: rewrite the whole shell to use tokens instead of raw strings
tab completion is still running with raw strings and that has to be fixed in the future.
This commit is contained in:
330
userspace/programs/Shell/Execute.cpp
Normal file
330
userspace/programs/Shell/Execute.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
#include "Builtin.h"
|
||||
#include "Execute.h"
|
||||
#include "TokenParser.h"
|
||||
|
||||
#include <BAN/ScopeGuard.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CHECK_FD_OR_PERROR_AND_EXIT(oldfd, newfd) ({ if ((oldfd) != (newfd) && dup2((oldfd), (newfd)) == -1) { perror("dup2"); exit(errno); } })
|
||||
#define TRY_OR_PERROR_AND_BREAK(expr) ({ auto&& eval = (expr); if (eval.is_error()) { fprintf(stderr, "%s\n", eval.error().get_message()); continue; } eval.release_value(); })
|
||||
#define TRY_OR_EXIT(expr) ({ auto&& eval = (expr); if (eval.is_error()) exit(eval.error().get_error_code()); eval.release_value(); })
|
||||
|
||||
static BAN::ErrorOr<BAN::String> find_absolute_path_of_executable(const BAN::String& command)
|
||||
{
|
||||
if (command.size() >= PATH_MAX)
|
||||
return BAN::Error::from_errno(ENAMETOOLONG);
|
||||
|
||||
const auto check_executable_file =
|
||||
[](const char* path) -> BAN::ErrorOr<void>
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(path, &st) == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
||||
return BAN::Error::from_errno(ENOEXEC);
|
||||
return {};
|
||||
};
|
||||
|
||||
if (command.sv().contains('/'))
|
||||
{
|
||||
TRY(check_executable_file(command.data()));
|
||||
return TRY(BAN::String::formatted("{}", command));
|
||||
}
|
||||
|
||||
const char* path_env = getenv("PATH");
|
||||
if (path_env == nullptr)
|
||||
return BAN::Error::from_errno(ENOENT);
|
||||
|
||||
auto path_dirs = TRY(BAN::StringView(path_env).split(':'));
|
||||
for (auto path_dir : path_dirs)
|
||||
{
|
||||
const auto absolute_path = TRY(BAN::String::formatted("{}/{}", path_dir, command));
|
||||
|
||||
auto check_result = check_executable_file(absolute_path.data());
|
||||
if (!check_result.is_error())
|
||||
return absolute_path;
|
||||
|
||||
if (check_result.error().get_error_code() == ENOENT)
|
||||
continue;
|
||||
return check_result.release_error();
|
||||
}
|
||||
|
||||
return BAN::Error::from_errno(ENOENT);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<Execute::ExecuteResult> Execute::execute_command_no_wait(const InternalCommand& command)
|
||||
{
|
||||
ASSERT(!command.arguments.empty());
|
||||
|
||||
if (command.command.has<Builtin::BuiltinCommand>() && !command.background)
|
||||
{
|
||||
const auto& builtin = command.command.get<Builtin::BuiltinCommand>();
|
||||
if (builtin.immediate)
|
||||
{
|
||||
return ExecuteResult {
|
||||
.pid = -1,
|
||||
.exit_code = TRY(builtin.execute(*this, command.arguments, command.fd_in, command.fd_out))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const pid_t child_pid = fork();
|
||||
if (child_pid == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
if (child_pid == 0)
|
||||
{
|
||||
if (command.command.has<Builtin::BuiltinCommand>())
|
||||
{
|
||||
auto builtin_ret = command.command.get<Builtin::BuiltinCommand>().execute(*this, command.arguments, command.fd_in, command.fd_out);
|
||||
if (builtin_ret.is_error())
|
||||
exit(builtin_ret.error().get_error_code());
|
||||
exit(builtin_ret.value());
|
||||
}
|
||||
|
||||
BAN::Vector<const char*> exec_args;
|
||||
TRY_OR_EXIT(exec_args.reserve(command.arguments.size() + 1));
|
||||
for (const auto& argument : command.arguments)
|
||||
TRY_OR_EXIT(exec_args.push_back(argument.data()));
|
||||
TRY_OR_EXIT(exec_args.push_back(nullptr));
|
||||
|
||||
CHECK_FD_OR_PERROR_AND_EXIT(command.fd_in, STDIN_FILENO);
|
||||
CHECK_FD_OR_PERROR_AND_EXIT(command.fd_out, STDOUT_FILENO);
|
||||
|
||||
execv(command.command.get<BAN::String>().data(), const_cast<char* const*>(exec_args.data()));
|
||||
exit(errno);
|
||||
}
|
||||
|
||||
if (setpgid(child_pid, command.pgrp ? command.pgrp : child_pid))
|
||||
perror("setpgid");
|
||||
if (!command.background && command.pgrp == 0 && isatty(STDIN_FILENO))
|
||||
if (tcsetpgrp(STDIN_FILENO, child_pid) == -1)
|
||||
perror("tcsetpgrp");
|
||||
|
||||
return ExecuteResult {
|
||||
.pid = child_pid,
|
||||
.exit_code = -1,
|
||||
};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<int> Execute::execute_command_sync(BAN::Span<const BAN::String> arguments, int fd_in, int fd_out)
|
||||
{
|
||||
if (arguments.empty())
|
||||
return 0;
|
||||
|
||||
InternalCommand command {
|
||||
.command = {},
|
||||
.arguments = arguments,
|
||||
.fd_in = fd_in,
|
||||
.fd_out = fd_out,
|
||||
.background = false,
|
||||
.pgrp = getpgrp(),
|
||||
};
|
||||
|
||||
if (const auto* builtin = Builtin::get().find_builtin(arguments[0]))
|
||||
command.command = *builtin;
|
||||
else
|
||||
{
|
||||
auto absolute_path_or_error = find_absolute_path_of_executable(arguments[0]);
|
||||
if (absolute_path_or_error.is_error())
|
||||
{
|
||||
if (absolute_path_or_error.error().get_error_code() == ENOENT)
|
||||
{
|
||||
fprintf(stderr, "command not found: %s\n", arguments[0].data());
|
||||
return 127;
|
||||
}
|
||||
fprintf(stderr, "could not execute command: %s\n", absolute_path_or_error.error().get_message());
|
||||
return 126;
|
||||
}
|
||||
command.command = absolute_path_or_error.release_value();
|
||||
}
|
||||
|
||||
const auto execute_result = TRY(execute_command_no_wait(command));
|
||||
if (execute_result.pid == -1)
|
||||
return execute_result.exit_code;
|
||||
|
||||
int status;
|
||||
if (waitpid(execute_result.pid, &status, 0) == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
if (!WIFSIGNALED(status))
|
||||
return WEXITSTATUS(status);
|
||||
return 128 + WTERMSIG(status);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Execute::execute_command(const PipedCommand& piped_command)
|
||||
{
|
||||
ASSERT(!piped_command.commands.empty());
|
||||
|
||||
int last_pipe_rd = STDIN_FILENO;
|
||||
|
||||
BAN::Vector<pid_t> child_pids;
|
||||
TRY(child_pids.resize(piped_command.commands.size(), 0));
|
||||
|
||||
BAN::Vector<int> child_codes;
|
||||
TRY(child_codes.resize(piped_command.commands.size(), 126));
|
||||
|
||||
for (size_t i = 0; i < piped_command.commands.size(); i++)
|
||||
{
|
||||
int new_pipe[2] { STDIN_FILENO, STDOUT_FILENO };
|
||||
if (i != piped_command.commands.size() - 1)
|
||||
if (pipe(new_pipe) == -1)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
BAN::ScopeGuard pipe_closer(
|
||||
[&]()
|
||||
{
|
||||
if (new_pipe[1] != STDOUT_FILENO)
|
||||
close(new_pipe[1]);
|
||||
if (last_pipe_rd != STDIN_FILENO)
|
||||
close(last_pipe_rd);
|
||||
last_pipe_rd = new_pipe[0];
|
||||
}
|
||||
);
|
||||
|
||||
const int fd_in = last_pipe_rd;
|
||||
const int fd_out = new_pipe[1];
|
||||
|
||||
auto arguments = TRY_OR_PERROR_AND_BREAK(piped_command.commands[i].evaluate_arguments(*this));
|
||||
|
||||
InternalCommand command {
|
||||
.command = {},
|
||||
.arguments = arguments.span(),
|
||||
.fd_in = fd_in,
|
||||
.fd_out = fd_out,
|
||||
.background = piped_command.background,
|
||||
.pgrp = child_pids.front(),
|
||||
};
|
||||
|
||||
if (const auto* builtin = Builtin::get().find_builtin(arguments[0]))
|
||||
command.command = *builtin;
|
||||
else
|
||||
{
|
||||
auto absolute_path_or_error = find_absolute_path_of_executable(arguments[0]);
|
||||
if (absolute_path_or_error.is_error())
|
||||
{
|
||||
if (absolute_path_or_error.error().get_error_code() == ENOENT)
|
||||
{
|
||||
fprintf(stderr, "command not found: %s\n", arguments[0].data());
|
||||
child_codes[i] = 127;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "could not execute command: %s\n", absolute_path_or_error.error().get_message());
|
||||
child_codes[i] = 126;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
command.command = absolute_path_or_error.release_value();
|
||||
}
|
||||
|
||||
auto execute_result = TRY_OR_PERROR_AND_BREAK(execute_command_no_wait(command));
|
||||
if (execute_result.pid == -1)
|
||||
child_codes[i] = execute_result.exit_code;
|
||||
else
|
||||
child_pids[i] = execute_result.pid;
|
||||
}
|
||||
|
||||
if (last_pipe_rd != STDIN_FILENO)
|
||||
close(last_pipe_rd);
|
||||
|
||||
if (piped_command.background)
|
||||
return {};
|
||||
|
||||
for (size_t i = 0; i < piped_command.commands.size(); i++)
|
||||
{
|
||||
if (child_pids[i] == 0)
|
||||
continue;
|
||||
|
||||
int status = 0;
|
||||
if (waitpid(child_pids[i], &status, 0) == -1)
|
||||
perror("waitpid");
|
||||
|
||||
if (WIFEXITED(status))
|
||||
child_codes[i] = WEXITSTATUS(status);
|
||||
else if (WIFSIGNALED(status))
|
||||
child_codes[i] = 128 + WTERMSIG(status);
|
||||
else
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (isatty(STDIN_FILENO) && tcsetpgrp(0, getpgrp()) == -1)
|
||||
perror("tcsetpgrp");
|
||||
m_last_return_value = child_codes.back();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Execute::execute_command(const CommandTree& command_tree)
|
||||
{
|
||||
for (const auto& [command, condition] : command_tree.commands)
|
||||
{
|
||||
bool should_run = false;
|
||||
switch (condition)
|
||||
{
|
||||
case ConditionalCommand::Condition::Always:
|
||||
should_run = true;
|
||||
break;
|
||||
case ConditionalCommand::Condition::OnFailure:
|
||||
should_run = (m_last_return_value != 0);
|
||||
break;
|
||||
case ConditionalCommand::Condition::OnSuccess:
|
||||
should_run = (m_last_return_value == 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!should_run)
|
||||
continue;
|
||||
|
||||
TRY(execute_command(command));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Execute::source_script(BAN::StringView path)
|
||||
{
|
||||
BAN::Vector<BAN::String> script_lines;
|
||||
|
||||
{
|
||||
FILE* fp = fopen(path.data(), "r");
|
||||
if (fp == nullptr)
|
||||
return BAN::Error::from_errno(errno);
|
||||
|
||||
BAN::String current;
|
||||
char temp_buffer[128];
|
||||
while (fgets(temp_buffer, sizeof(temp_buffer), fp))
|
||||
{
|
||||
TRY(current.append(temp_buffer));
|
||||
if (current.back() != '\n')
|
||||
continue;
|
||||
current.pop_back();
|
||||
|
||||
if (!current.empty())
|
||||
TRY(script_lines.push_back(BAN::move(current)));
|
||||
current.clear();
|
||||
}
|
||||
|
||||
if (!current.empty())
|
||||
TRY(script_lines.push_back(BAN::move(current)));
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
TokenParser parser(
|
||||
[&](BAN::Optional<BAN::StringView>) -> BAN::Optional<BAN::String>
|
||||
{
|
||||
if (index >= script_lines.size())
|
||||
return {};
|
||||
return script_lines[index++];
|
||||
}
|
||||
);
|
||||
if (!parser.main_loop(true))
|
||||
return BAN::Error::from_literal("oop");
|
||||
return {};
|
||||
}
|
||||
Reference in New Issue
Block a user