#include "Alias.h" #include "Builtin.h" #include "Execute.h" #include #include #include #define ERROR_RETURN(__msg, __ret) do { perror(__msg); return __ret; } while (false) extern char** environ; void Builtin::initialize() { MUST(m_builtin_commands.emplace("clear"_sv, [](Execute&, BAN::Span, FILE*, FILE* fout) -> int { fprintf(fout, "\e[H\e[3J\e[2J"); fflush(fout); return 0; }, true )); MUST(m_builtin_commands.emplace("exit"_sv, [](Execute&, BAN::Span arguments, FILE*, FILE*) -> int { int exit_code = 0; if (arguments.size() > 1) { auto exit_string = arguments[1].sv(); for (size_t i = 0; i < exit_string.size() && isdigit(exit_string[i]); i++) exit_code = (exit_code * 10) + (exit_string[i] - '0'); } exit(exit_code); ASSERT_NOT_REACHED(); }, true )); MUST(m_builtin_commands.emplace("export"_sv, [](Execute&, BAN::Span arguments, FILE*, FILE*) -> int { bool first = false; for (const auto& argument : arguments) { if (first) { first = false; continue; } auto split = MUST(argument.sv().split('=', true)); if (split.size() != 2) continue; if (setenv(BAN::String(split[0]).data(), BAN::String(split[1]).data(), true) == -1) ERROR_RETURN("setenv", 1); } return 0; }, true )); MUST(m_builtin_commands.emplace("unset"_sv, [](Execute&, BAN::Span arguments, FILE*, FILE*) -> int { for (const auto& argument : arguments) if (unsetenv(argument.data()) == -1) ERROR_RETURN("unsetenv", 1); return 0; }, true )); MUST(m_builtin_commands.emplace("alias"_sv, [](Execute&, BAN::Span arguments, FILE*, FILE* fout) -> int { if (arguments.size() == 1) { Alias::get().for_each_alias( [fout](BAN::StringView name, BAN::StringView value) -> BAN::Iteration { fprintf(fout, "%.*s='%.*s'\n", (int)name.size(), name.data(), (int)value.size(), value.data() ); return BAN::Iteration::Continue; } ); return 0; } for (size_t i = 1; i < arguments.size(); i++) { auto idx = arguments[i].sv().find('='); if (idx.has_value() && idx.value() == 0) continue; if (!idx.has_value()) { auto value = Alias::get().get_alias(arguments[i]); if (value.has_value()) fprintf(fout, "%s='%.*s'\n", arguments[i].data(), (int)value->size(), value->data()); } else { auto alias = arguments[i].sv().substring(0, idx.value()); auto value = arguments[i].sv().substring(idx.value() + 1); if (auto ret = Alias::get().set_alias(alias, value); ret.is_error()) fprintf(stderr, "could not set alias: %s\n", ret.error().get_message()); } } return 0; }, true )); MUST(m_builtin_commands.emplace("source"_sv, [](Execute& execute, BAN::Span arguments, FILE*, FILE* fout) -> int { if (arguments.size() != 2) { fprintf(fout, "usage: source FILE\n"); return 1; } if (execute.source_script(arguments[1]).is_error()) return 1; return 0; }, true )); MUST(m_builtin_commands.emplace("env"_sv, [](Execute&, BAN::Span, FILE*, FILE* fout) -> int { char** current = environ; while (current && *current) fprintf(fout, "%s\n", *current++); return 0; }, true )); MUST(m_builtin_commands.emplace("cd"_sv, [](Execute&, BAN::Span arguments, FILE*, FILE* fout) -> int { if (arguments.size() > 2) { fprintf(fout, "cd: too many arguments\n"); return 1; } BAN::StringView path; if (arguments.size() == 1) { if (const char* path_env = getenv("HOME")) path = path_env; else return 0; } else path = arguments[1]; if (chdir(path.data()) == -1) ERROR_RETURN("chdir", 1); return 0; }, true )); MUST(m_builtin_commands.emplace("time"_sv, [](Execute& execute, BAN::Span arguments, FILE* fin, FILE* fout) -> int { timespec start, end; if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) ERROR_RETURN("clock_gettime", 1); auto execute_ret = execute.execute_command_sync(arguments.slice(1), fileno(fin), fileno(fout)); if (clock_gettime(CLOCK_MONOTONIC, &end) == -1) ERROR_RETURN("clock_gettime", 1); uint64_t total_ns = 0; total_ns += (end.tv_sec - start.tv_sec) * 1'000'000'000; total_ns += end.tv_nsec - start.tv_nsec; int secs = total_ns / 1'000'000'000; int msecs = (total_ns % 1'000'000'000) / 1'000'000; fprintf(fout, "took %d.%03d s\n", secs, msecs); if (execute_ret.is_error()) return 256 + execute_ret.error().get_error_code(); return execute_ret.value(); }, false )); } void Builtin::for_each_builtin(BAN::Function callback) const { for (const auto& [name, function] : m_builtin_commands) { switch (callback(name.sv(), function)) { case BAN::Iteration::Break: break; case BAN::Iteration::Continue: continue;; } break; } } const Builtin::BuiltinCommand* Builtin::find_builtin(const BAN::String& name) const { auto it = m_builtin_commands.find(name); if (it == m_builtin_commands.end()) return nullptr; return &it->value; } BAN::ErrorOr Builtin::BuiltinCommand::execute(Execute& execute, BAN::Span arguments, int fd_in, int fd_out) const { const auto fd_to_file = [](int fd, FILE* file, const char* mode) -> BAN::ErrorOr { if (fd == fileno(file)) return file; int fd_dup = dup(fd); if (fd_dup == -1) return BAN::Error::from_errno(errno); file = fdopen(fd_dup, mode); if (file == nullptr) return BAN::Error::from_errno(errno); return file; }; FILE* fin = TRY(fd_to_file(fd_in, stdin, "r")); FILE* fout = TRY(fd_to_file(fd_out, stdout, "w")); int ret = function(execute, arguments, fin, fout); if (fileno(fin) != fd_in ) fclose(fin); if (fileno(fout) != fd_out) fclose(fout); return ret; }