diff --git a/CMakeLists.txt b/CMakeLists.txt index 49367fc9..99585872 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,3 +48,4 @@ add_subdirectory(libc) add_subdirectory(kernel) add_subdirectory(apps) add_subdirectory(tests) +add_subdirectory(shell) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 27f12d56..caa2a70b 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -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) diff --git a/libluna/include/luna/String.h b/libluna/include/luna/String.h index dc330a46..23c0846f 100644 --- a/libluna/include/luna/String.h +++ b/libluna/include/luna/String.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -129,3 +130,5 @@ class String void empty(); }; + +template <> u64 hash(const String& value, u64 salt); diff --git a/libluna/include/luna/StringView.h b/libluna/include/luna/StringView.h index cc51e5c9..7f12b0db 100644 --- a/libluna/include/luna/StringView.h +++ b/libluna/include/luna/StringView.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -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); diff --git a/libluna/src/String.cpp b/libluna/src/String.cpp index a2466f66..e606e77a 100644 --- a/libluna/src/String.cpp +++ b/libluna/src/String.cpp @@ -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); +} diff --git a/libluna/src/StringView.cpp b/libluna/src/StringView.cpp index 04f0db6b..c8209675 100644 --- a/libluna/src/StringView.cpp +++ b/libluna/src/StringView.cpp @@ -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); +} diff --git a/libos/include/os/ArgumentParser.h b/libos/include/os/ArgumentParser.h index cb944b52..e1998064 100644 --- a/libos/include/os/ArgumentParser.h +++ b/libos/include/os/ArgumentParser.h @@ -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 Whether the operation succeeded. + * @return Result 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 parse(int argc, char* const* argv); + Result parse(int argc, char* const* argv); /** * @brief A program's copyright and version information. @@ -155,13 +156,23 @@ namespace os * "Try running ' -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 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 usage(StringView program_name); - void version(); + Result version(); Vector m_positional_args; Vector 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 }; }; } diff --git a/libos/src/ArgumentParser.cpp b/libos/src/ArgumentParser.cpp index 49542816..ad75195f 100644 --- a/libos/src/ArgumentParser.cpp +++ b/libos/src/ArgumentParser.cpp @@ -126,7 +126,7 @@ namespace os return arg.length() > 2 && arg[0] == '-' && arg[1] == '-'; } - Result ArgumentParser::parse(int argc, char* const* argv) + Result 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 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 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 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 {}; } } diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt new file mode 100644 index 00000000..4d9fffa6 --- /dev/null +++ b/shell/CMakeLists.txt @@ -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) diff --git a/shell/builtin/cd.cpp b/shell/builtin/cd.cpp new file mode 100644 index 00000000..6229194b --- /dev/null +++ b/shell/builtin/cd.cpp @@ -0,0 +1,24 @@ +#include "sh.h" +#include +#include +#include + +shell_builtin_t builtin_cd = [](int argc, char** argv) -> Result { + 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 {}; +}; diff --git a/apps/sh.cpp b/shell/main.cpp similarity index 74% rename from apps/sh.cpp rename to shell/main.cpp index 6da9da8e..2a795633 100644 --- a/apps/sh.cpp +++ b/shell/main.cpp @@ -1,3 +1,4 @@ +#include "sh.h" #include #include #include @@ -15,6 +16,12 @@ #include #include +#include + +extern shell_builtin_t builtin_cd; + +static HashMap s_builtins; + using os::File; static Result> split_command_into_args(StringView cmd) @@ -32,6 +39,15 @@ static Result execute_command(StringView command) return os::Process::exec(args[0].view(), args.slice()); } +static Result 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 luna_main(int argc, char** argv) tcsetpgrp(STDIN_FILENO, getpgid(0)); } + init_builtins(); + while (1) { if (interactive) @@ -117,20 +135,30 @@ Result 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 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(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 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)) { diff --git a/shell/sh.h b/shell/sh.h new file mode 100644 index 00000000..64ad5f11 --- /dev/null +++ b/shell/sh.h @@ -0,0 +1,4 @@ +#pragma once +#include + +typedef Result (*shell_builtin_t)(int, char**);