sh: Add a system to easily add flexible shell builtins
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
apio 2023-07-21 20:44:01 +02:00
parent ff9e01641e
commit 4439ef8ec6
Signed by: apio
GPG Key ID: B8A7D06E42258954
12 changed files with 155 additions and 30 deletions

View File

@ -48,3 +48,4 @@ add_subdirectory(libc)
add_subdirectory(kernel)
add_subdirectory(apps)
add_subdirectory(tests)
add_subdirectory(shell)

View File

@ -16,7 +16,6 @@ install(TARGETS preinit DESTINATION ${LUNA_ROOT}/initrd/bin)
luna_app(init.cpp init)
luna_app(env.cpp env)
luna_app(su.cpp su)
luna_app(sh.cpp sh)
luna_app(cat.cpp cat)
luna_app(date.cpp date)
luna_app(edit.cpp edit)

View File

@ -1,4 +1,5 @@
#pragma once
#include <luna/Hash.h>
#include <luna/Result.h>
#include <luna/StringView.h>
#include <stdarg.h>
@ -129,3 +130,5 @@ class String
void empty();
};
template <> u64 hash(const String& value, u64 salt);

View File

@ -1,4 +1,5 @@
#pragma once
#include <luna/Hash.h>
#include <luna/Result.h>
#include <luna/Vector.h>
@ -75,3 +76,5 @@ inline StringView operator""_sv(const char* cstring, usize length)
{
return StringView { cstring, length };
}
template <> u64 hash(const StringView& value, u64 salt);

View File

@ -202,3 +202,8 @@ int String::compare(const String* a, const String* b)
{
return strcmp(a->chars(), b->chars());
}
template <> u64 hash(const String& value, u64 salt)
{
return hash_memory(value.chars(), value.length(), salt);
}

View File

@ -153,3 +153,8 @@ StringView StringView::from_fixed_size_cstring(const char* string, usize max)
{
return { string, strnlen(string, max) };
}
template <> u64 hash(const StringView& value, u64 salt)
{
return hash_memory(value.chars(), value.length(), salt);
}

View File

@ -115,9 +115,10 @@ namespace os
*
* @param argc The argc value passed to main() or luna_main().
* @param argv The argv value passed to main() or luna_main().
* @return Result<void> Whether the operation succeeded.
* @return Result<bool> Whether the operation succeeded, and if the arguments were correctly parsed (only needed
* if should_exit_on_bad_usage is not set, otherwise the success result would always be true).
*/
Result<void> parse(int argc, char* const* argv);
Result<bool> parse(int argc, char* const* argv);
/**
* @brief A program's copyright and version information.
@ -155,13 +156,23 @@ namespace os
* "Try running '<program> -h' for more information." if -h has not been overridden.
* If both have been overridden, no output is shown.
*
* Then, the program exits with a non-zero exit code.
* Then, the program exits with a non-zero exit code, if should_exit_on_bad_usage is set.
* This function is designed to be used when the program detects an invalid argument value after parse() has run
* successfully.
*
* @param program_name The program name to show (usually argv[0]).
*/
void short_usage(StringView program_name);
Result<void> short_usage(StringView program_name);
/**
* @brief Set whether the parser should exit the program when an incorrect usage is detected.
*
* @param b The boolean value to set.
*/
void set_should_exit_on_bad_usage(bool b)
{
m_should_exit_on_bad_usage = b;
}
private:
struct PositionalArgument
@ -190,7 +201,7 @@ namespace os
Result<void> usage(StringView program_name);
void version();
Result<void> version();
Vector<PositionalArgument> m_positional_args;
Vector<SwitchArgument> m_switch_args;
@ -203,5 +214,6 @@ namespace os
bool m_add_long_help_flag { false };
bool m_add_short_version_flag { false };
bool m_add_long_version_flag { false };
bool m_should_exit_on_bad_usage { true };
};
}

View File

@ -126,7 +126,7 @@ namespace os
return arg.length() > 2 && arg[0] == '-' && arg[1] == '-';
}
Result<void> ArgumentParser::parse(int argc, char* const* argv)
Result<bool> ArgumentParser::parse(int argc, char* const* argv)
{
StringView program_name = argv[0];
@ -163,8 +163,17 @@ namespace os
bool found = false;
if (m_add_long_help_flag && flag == "help"_sv) { TRY(usage(program_name)); }
if (m_add_long_version_flag && flag == "version"_sv) { version(); }
if (m_add_long_help_flag && flag == "help"_sv)
{
TRY(usage(program_name));
return false;
}
if (m_add_long_version_flag && flag == "version"_sv)
{
version();
return false;
}
for (const auto& current : m_switch_args)
{
@ -196,6 +205,7 @@ namespace os
os::eprintln("%s: unrecognized option '%s'", program_name.chars(), arg.chars());
short_usage(program_name);
return false;
}
else if (looks_like_short_flag(arg))
{
@ -206,8 +216,17 @@ namespace os
char c = flags[j];
bool found = false;
if (m_add_short_help_flag && c == 'h') { TRY(usage(program_name)); }
if (m_add_short_version_flag && c == 'v') { version(); }
if (m_add_short_help_flag && c == 'h')
{
TRY(usage(program_name));
return false;
}
if (m_add_short_version_flag && c == 'v')
{
version();
return false;
}
// Last flag, this could be a value flag
if (j + 1 == flags.length())
@ -244,6 +263,7 @@ namespace os
os::eprintln("%s: invalid option -- '%c'", program_name.chars(), c);
short_usage(program_name);
return false;
}
continue;
@ -276,6 +296,7 @@ namespace os
os::eprintln("%s: option '-%c' requires an argument", program_name.chars(),
current_value_argument->short_flag);
short_usage(program_name);
return false;
}
if (is_parsing_argument_vector)
@ -292,6 +313,7 @@ namespace os
{
os::eprintln("%s: required argument '%s' not provided", program_name.chars(), arg.name.chars());
short_usage(program_name);
return false;
}
else { *arg.out = arg.fallback; }
}
@ -308,6 +330,7 @@ namespace os
{
os::eprintln("%s: required argument '%s' not provided", program_name.chars(), arg.name.chars());
short_usage(program_name);
return false;
}
else
{
@ -321,7 +344,7 @@ namespace os
}
}
return {};
return true;
}
Result<void> ArgumentParser::usage(StringView program_name)
@ -400,10 +423,12 @@ namespace os
else
printf(" -%-25c %s\n", 'v', "show version information and exit");
exit(0);
if (m_should_exit_on_bad_usage) exit(0);
else
return {};
}
void ArgumentParser::version()
Result<void> ArgumentParser::version()
{
if (m_program_info.package.is_empty())
printf("%s %s\n", m_program_info.name.chars(), m_program_info.version.chars());
@ -415,15 +440,19 @@ namespace os
if (!m_program_info.authors.is_empty()) printf("\n%s\n", m_program_info.authors.chars());
exit(0);
if (m_should_exit_on_bad_usage) exit(0);
else
return {};
}
void ArgumentParser::short_usage(StringView program_name)
Result<void> ArgumentParser::short_usage(StringView program_name)
{
if (m_add_short_help_flag || m_add_long_help_flag)
os::eprintln("Try running '%s %s' for more information.", program_name.chars(),
m_add_long_help_flag ? "--help" : "-h");
exit(1);
if (m_should_exit_on_bad_usage) exit(1);
else
return {};
}
}

12
shell/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
set(SOURCES
main.cpp
sh.h
builtin/cd.cpp
)
add_executable(sh ${SOURCES})
target_compile_options(sh PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
add_dependencies(sh libc)
target_include_directories(sh PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(sh PRIVATE os)
install(TARGETS sh DESTINATION ${LUNA_BASE}/usr/bin)

24
shell/builtin/cd.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "sh.h"
#include <luna/String.h>
#include <os/ArgumentParser.h>
#include <os/FileSystem.h>
shell_builtin_t builtin_cd = [](int argc, char** argv) -> Result<void> {
StringView directory;
os::ArgumentParser parser;
parser.set_should_exit_on_bad_usage(false);
parser.add_description("Change working directory (shell builtin)"_sv);
parser.add_system_program_info("cd"_sv);
parser.add_positional_argument(directory, "directory"_sv, false);
if (!TRY(parser.parse(argc, argv))) return {};
if (!directory.is_empty()) TRY(os::FileSystem::change_directory(directory));
else
{
auto home = TRY(os::FileSystem::home_directory());
TRY(os::FileSystem::change_directory(home.view()));
}
return {};
};

View File

@ -1,3 +1,4 @@
#include "sh.h"
#include <errno.h>
#include <luna/String.h>
#include <luna/Vector.h>
@ -15,6 +16,12 @@
#include <termios.h>
#include <unistd.h>
#include <luna/HashMap.h>
extern shell_builtin_t builtin_cd;
static HashMap<StringView, shell_builtin_t> s_builtins;
using os::File;
static Result<Vector<String>> split_command_into_args(StringView cmd)
@ -32,6 +39,15 @@ static Result<void> execute_command(StringView command)
return os::Process::exec(args[0].view(), args.slice());
}
static Result<void> init_builtins()
{
s_builtins = {};
TRY(s_builtins.try_set("cd"_sv, builtin_cd));
return {};
}
// Do nothing, but we need a handler so read() returns EINTR.
static void sigint_handler(int)
{
@ -87,6 +103,8 @@ Result<int> luna_main(int argc, char** argv)
tcsetpgrp(STDIN_FILENO, getpgid(0));
}
init_builtins();
while (1)
{
if (interactive)
@ -117,20 +135,30 @@ Result<int> luna_main(int argc, char** argv)
if (strcspn(cmd.chars(), " #") == 0) continue;
if (!strncmp(cmd.chars(), "cd", 2))
{
auto args = TRY(split_command_into_args(cmd.view()));
check(args[0].view() == "cd");
auto args = TRY(split_command_into_args(cmd.view()));
if (args.size() < 1) continue;
if (args.size() == 1)
if (!strchr(args[0].chars(), '/'))
{
auto maybe_builtin = s_builtins.try_get(args[0].view());
if (maybe_builtin.has_value())
{
auto home = TRY(os::FileSystem::home_directory());
TRY(os::FileSystem::change_directory(home.view()));
auto builtin = maybe_builtin.value();
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())
{
errno = rc.error();
perror(argv[0]);
}
continue;
}
TRY(os::FileSystem::change_directory(args[1].view()));
continue;
}
pid_t child = TRY(os::Process::fork());
@ -140,15 +168,15 @@ Result<int> luna_main(int argc, char** argv)
if (interactive)
{
setpgid(0, 0);
tcsetpgrp(STDIN_FILENO, getpid());
tcsetpgrp(input_file->fd(), getpid());
}
TRY(execute_command(cmd.view()));
TRY(os::Process::exec(args[0].view(), args.slice()));
}
int status;
TRY(os::Process::wait(child, &status));
if (interactive) tcsetpgrp(STDIN_FILENO, getpgid(0));
if (interactive) tcsetpgrp(input_file->fd(), getpgid(0));
if (WIFSIGNALED(status))
{

4
shell/sh.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include <luna/Result.h>
typedef Result<void> (*shell_builtin_t)(int, char**);