307 lines
7.7 KiB
C++
307 lines
7.7 KiB
C++
#include "Alias.h"
|
|
#include "Builtin.h"
|
|
#include "Execute.h"
|
|
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define ERROR_RETURN(__msg, __ret) do { perror(__msg); return __ret; } while (false)
|
|
|
|
void Builtin::initialize()
|
|
{
|
|
MUST(m_builtin_commands.emplace("clear"_sv,
|
|
[](Execute&, BAN::Span<const BAN::String>, 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<const BAN::String> 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<const BAN::String> 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<const BAN::String> 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<const BAN::String> 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<const BAN::String> 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("cd"_sv,
|
|
[](Execute&, BAN::Span<const BAN::String> 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("type"_sv,
|
|
[](Execute&, BAN::Span<const BAN::String> arguments, FILE*, FILE* fout) -> int
|
|
{
|
|
const auto is_executable_file =
|
|
[](const char* path) -> bool
|
|
{
|
|
struct stat st;
|
|
if (stat(path, &st) == -1)
|
|
return false;
|
|
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
|
return false;
|
|
return true;
|
|
};
|
|
|
|
if (!arguments.empty())
|
|
arguments = arguments.slice(1);
|
|
|
|
BAN::Vector<BAN::StringView> path_dirs;
|
|
if (const char* path_env = getenv("PATH"))
|
|
if (auto split_ret = BAN::StringView(path_env ? path_env : "").split(':'); !split_ret.is_error())
|
|
path_dirs = split_ret.release_value();
|
|
|
|
for (const auto& argument : arguments)
|
|
{
|
|
if (auto alias = Alias::get().get_alias(argument); alias.has_value())
|
|
{
|
|
fprintf(fout, "%s is an alias for %s\n", argument.data(), alias->data());
|
|
continue;
|
|
}
|
|
|
|
if (Builtin::get().find_builtin(argument))
|
|
{
|
|
fprintf(fout, "%s is a shell builtin\n", argument.data());
|
|
continue;
|
|
}
|
|
|
|
if (argument.sv().contains('/'))
|
|
{
|
|
if (is_executable_file(argument.data()))
|
|
{
|
|
fprintf(fout, "%s is %s\n", argument.data(), argument.data());
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool found = false;
|
|
for (const auto& path_dir : path_dirs)
|
|
{
|
|
char path_buffer[PATH_MAX];
|
|
memcpy(path_buffer, path_dir.data(), path_dir.size());
|
|
memcpy(path_buffer + path_dir.size(), argument.data(), argument.size());
|
|
path_buffer[path_dir.size() + argument.size()] = '\0';
|
|
|
|
if (is_executable_file(path_buffer))
|
|
{
|
|
fprintf(fout, "%s is %s\n", argument.data(), path_buffer);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found)
|
|
continue;
|
|
|
|
}
|
|
|
|
fprintf(fout, "%s not found\n", argument.data());
|
|
}
|
|
|
|
return 0;
|
|
}, true
|
|
));
|
|
|
|
// FIXME: time should not actually be a builtin command but a shell reserved keyword
|
|
// e.g. `time foobar=lol sh -c 'echo $foobar'` should resolve set foobar env
|
|
MUST(m_builtin_commands.emplace("time"_sv,
|
|
[](Execute& execute, BAN::Span<const BAN::String> 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<BAN::Iteration(BAN::StringView, const BuiltinCommand&)> 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<int> Builtin::BuiltinCommand::execute(Execute& execute, BAN::Span<const BAN::String> arguments, int fd_in, int fd_out) const
|
|
{
|
|
const auto fd_to_file =
|
|
[](int fd, FILE* file, const char* mode) -> BAN::ErrorOr<FILE*>
|
|
{
|
|
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;
|
|
}
|