banan-os/userspace/programs/Shell/Execute.cpp

331 lines
8.7 KiB
C++
Raw Normal View History

#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 {};
}