sh: Add a system to easily add flexible shell builtins
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
ff9e01641e
commit
4439ef8ec6
@ -48,3 +48,4 @@ add_subdirectory(libc)
|
||||
add_subdirectory(kernel)
|
||||
add_subdirectory(apps)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(shell)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
@ -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
12
shell/CMakeLists.txt
Normal 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
24
shell/builtin/cd.cpp
Normal 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 {};
|
||||
};
|
@ -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
4
shell/sh.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <luna/Result.h>
|
||||
|
||||
typedef Result<void> (*shell_builtin_t)(int, char**);
|
Loading…
Reference in New Issue
Block a user